-1

I have this piece of code

import abc


class SingletonABCMeta(abc.ABCMeta):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonABCMeta, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Cabc(abc.ABC):
    __metaclass__ = SingletonABCMeta

    @abc.abstractmethod
    def foo(self): pass


class C(Cabc):

    def __init__(self, a):
        print(f"initializing with {a}")
        self._a = a

    def foo(self):
        return self._a


class DoubleC(Cabc):

    def __init__(self, a):
        print(f"initializing with {a}")
        self._a = a

    def foo(self):
        return self._a * 2


if __name__ == "__main__":
    print(C(2).foo())
    print(DoubleC(2).foo())

    print(C(3).foo())
    print(DoubleC(3).foo())

it returns

initializing with 2
2
initializing with 2
4
initializing with 3
3
initializing with 3
6

But I expect it to return

initializing with 2
2
initializing with 2
4
2
4

How do I do this?

Amin Ba
  • 1,603
  • 1
  • 13
  • 38
  • Does this answer your question? [Creating a singleton in Python](https://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) – STerliakov May 11 '23 at 22:45
  • that one I read is not about abc class – Amin Ba May 11 '23 at 22:46
  • 2
    Hate to break it to you, but your class can still be instantiated more than once. So instead of all this rigamarole of defining a metaclass, why don't you just create a factory function that is cached with `@functools.cache`? It would be just as safe and a million times simpler – juanpa.arrivillaga May 11 '23 at 22:47
  • please explain here how can it be instantiated more than once https://stackoverflow.com/a/76232000/11065874 – Amin Ba May 11 '23 at 22:49
  • Believe me or not, but your `ABCMeta` implementation is no different from plain metaclass in the linked dupe... – STerliakov May 11 '23 at 22:49
  • 1
    @AminBa extremely easily. `c = object.__new__(C); c.__init__(3); print(c.foo())` – juanpa.arrivillaga May 11 '23 at 22:50
  • 1
    A lot of "design patterns" are really just "things we have to use because Java is unweidly, but in other languages, we would use a much easier approach, like just a regular factory function". (I didin't downvote btw, it just saddens me to see this sort of code in the wild) – juanpa.arrivillaga May 11 '23 at 22:50
  • @juanpa.arrivillaga if a (the input to init) is not mutable, you cannot use cache – Amin Ba May 11 '23 at 22:53
  • Ok, then you can just manually implement caching, doing pretty much what you are doing in your meta pass `__call__` – juanpa.arrivillaga May 11 '23 at 22:55
  • how can I manually do it in a factory function? – Amin Ba May 11 '23 at 22:57
  • Literally the same exact thing you do in `__call__` in your meta class – juanpa.arrivillaga May 11 '23 at 22:59

1 Answers1

0

figured it out

I changed from using __metaclass__ = SingletonABCMeta to the python3 version of it that is Cabc(abc.ABC, metaclass=SingletonABCMeta)

import abc


class SingletonABCMeta(abc.ABCMeta):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonABCMeta, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class Cabc(abc.ABC, metaclass=SingletonABCMeta):

    @abc.abstractmethod
    def foo(self): pass


class C(Cabc):

    def __init__(self, a):
        print(f"initializing with {a}")
        self._a = a

    def foo(self):
        return self._a


if __name__ == "__main__":
    print(C(2).foo())
    print(C(3).foo())

returns:

initializing with 2
2
2

But now that I think I should raise an exception if I do not want user to create a new instance of this class. Then I am going with this solution

import abc


class SingletonABCMeta(abc.ABCMeta):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonABCMeta, cls).__call__(*args, **kwargs)
            return cls._instances[cls]
        else:
            raise Exception("This is a singleton. no more than one instance of it can exist")


class Cabc(abc.ABC, metaclass=SingletonABCMeta):

    @abc.abstractmethod
    def foo(self): pass


class C(Cabc):

    def __init__(self, a):
        print(f"initializing with {a}")
        self._a = a

    def foo(self):
        return self._a


if __name__ == "__main__":
    print(C(2).foo())
    print(C(3).foo())
Amin Ba
  • 1,603
  • 1
  • 13
  • 38