简体   繁体   中英

singleton design pattern in Python with state

I'm trying to implement a singleton in Python, and after reading this post I find myself even more confused than before. There are way too many answers, and many of them have received their fair number of votes. Now the problem may not be that I have a singleton, but the fact that the state has to be initialized only once. I tried a couple of implementations SingletonA and SingletonB but I can't manage this to work. For my real problem, the __init__ function is quite heavy so I have to do it only once. This is what I have so far:

class ClassA:

    def __init__(self):

        # some state
        print("Creating state in A")
        self.X = 1.
        self.Y = 2.

class SingletonA(ClassA):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SingletonA, cls).__new__(
                                cls, *args, **kwargs)
        return cls._instance


class ClassB:

    __shared_state = {}

    def __init__(self):

        if not bool(ClassB.__shared_state):

            # some state
            print("Creating state in B")
            self.X = 1.
            self.Y = 2.

        self.__dict__ = self.__shared_state


def singleton(cls):
    obj = cls()
    # Always return the same object
    cls.__new__ = staticmethod(lambda cls: obj)
    # Disable __init__
    try:
        del cls.__init__
    except AttributeError:
        pass
    return cls

@singleton
class SingletonB(ClassB):
    pass

if __name__ == "__main__":

    a1 = SingletonA()
    a2 = SingletonA()
    if (id(a1) == id(a2)):
        print("Same",a1.X, a1.Y)
    else:
        print("Different",a1.X, a1.Y)

    b1 = SingletonB()
    b2 = SingletonB()
    if (id(b1) == id(b2)):
        print("Same",b1.X, b1.Y)
    else:
        print("Different",b1.X, b1.Y)

Now this is printing:

$ python singleton.py 
Creating state in B
Creating state in B
Same 1.0 2.0
Creating state in A
Creating state in A
Same 1.0 2.0

Pointing to the fact that indeed I have a singleton class, but I want to avoid the creation of state.

This cannot work, because __init__ method is called after object creation through __new__ . Extract from Python Language Reference

If __new__() returns an instance of cls, then the new instance's __init__() method will be invoked like __init__(self[, ...]) , where self is the new instance and the remaining arguments are the same as were passed to __new__() .

You should instead have a special initialization method distinct from __init__ and called at _instance creation.

Code could be (no need for a parent class, so I omitted it):

class SingletonA:
    _instance = None
    def __init__(self):

        # some state
        print("Creating dummy state in SingletonA")

    def _init(self):

        # some state
        print("Creating state in SingletonA")
        self.X = 1.
        self.Y = 2.
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(SingletonA, cls).__new__(
                cls, *args, **kwargs)
            cls._instance._init()
        return cls._instance

But in fact, you can simply build the instance at declaration time:

class SingletonA:
    def __init__(self):

        # some state
        print("Creating dummy state in SingletonA")

    def _init(self):

        # some state
        print("Creating state in A")
        self.X = 1.
        self.Y = 2.
    def __new__(cls, *args, **kwargs):
        return cls._instance

SingletonA._instance = super(SingletonA, SingletonA).__new__(SingletonA)
SingletonA._instance._init()

Both ways would cause following output (for SingletonA part):

Creating state in A
Creating dummy state in SingletonA
Creating dummy state in SingletonA
('Same', 1.0, 2.0)

Completely removing the __init__ method would cause one single initialization.

The annotation version could be:

class ClassB:

    def __init__(self):

            # some state
            print("Creating state in B")
            self.X = 1.
            self.Y = 2.


def singleton(cls):
    obj = cls()
    # Always return the same object
    cls._instance = obj
    cls.__new__ = staticmethod(lambda cls: cls._instance)
    # Disable __init__
    cls.__init__ = (lambda self: None)
    return cls

@singleton
class SingletonB(ClassB):
    pass

simply storing the singleton instance in the class itself

If I understand you correctly, this is all what you need:

# edited according to discussion in comments
class C:
    _shared_dict = None
    def __init__(self):
        if self._shared_dict is None:
            print("initializing")
            self.x = 1 
            self.y = 2 
            self.__class__._shared_dict = self.__dict__
        else:
            self.__dict__ = self._shared_dict

a=C()
b=C()

print(id(a), a.x, a.y)
print(id(b), b.x, b.y)

All instances will share the same data and this data will be computed only once.

Please note that you can refer to the shared data as self._shared_dict when reading, but must use the full name of a class attribute self.__class__._shared_dict when writing to it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM