This is the 8th day of my participation in the August More Text Challenge

Recognize the singleton pattern

meaning

The singleton pattern is a common software design pattern in which a class generates only one instance object. In other words, the singleton ensures that there is only one instance of a class, and that it instantiates itself and makes that instance available to the entire system. When we call the class to instantiate it from a different place in the program, an instance is created if an instance of the class doesn’t exist. This instance is returned if it already exists. For example, the recycle bin is the application of singleton mode. We all have a recycle bin on the desktop of the computer. In the whole operating system, the recycle bin can only have one instance, and the whole system uses this unique instance.

advantages

  • Because the singleton pattern has only one instance globally, it saves a lot of memory.
  • There is only one ACCESS point globally, which enables better data synchronization control and avoids multiple occupation.

Python implements the singleton pattern

Use the decorator approach

Function decorator mode
def singleton(cls) :

    Create a dictionary to store the instance object of the decorated class.
    def _singleton(*args, **kwargs) :
        # check whether this class has created any objects, if not, return the previously created object
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return _singleton

@singleton
class A(object) :
    def __init__(self, a=0) :
        self.a = a


a1 = A(1)
a2 = A(2)
The # id() function retrieves the memory address of an object. The same memory address is the same object
print(id(a1), id(a2))
Copy the code
Class decorator mode
class Singleton(object) :
    def __init__(self, cls) :
        self._cls = cls
        self._instance = {}

    def __call__(self) :
        if self._cls not in self._instance:
            self._instance[self._cls] = self._cls()
        return self._instance[self._cls]


@Singleton
class B(object) :
    def __init__(self) :
        pass

b1 = B()
b2 = B()
print(id(b1), id(b2))
Copy the code

Python decorators (Python decorators, Python decorators, Python decorators, Python decorators, Python decorators)

Implemented using a class

class Singleton(object) :
    def __init__(self, *args, **kwargs) :
        pass
        
    @classmethod
    def get_instance(cls, *args, **kwargs) :
        The # hasattr() function is used to determine whether an object has an attribute. In this case, the class has an _instance attribute
        if not hasattr(Singleton, '_instance' ):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


s1 = Singleton()  When creating instances in this way, singletons are not guaranteed
s2 = Singleton.get_instance()  Singletons can only be implemented if created in this way
s3 = Singleton()
s4 = Singleton.get_instance()

print(id(s1), id(s2), id(s3), id(s4))
Copy the code

Call get_instance() to create an object. The get_instance method will determine if an object has been created before, and if it has, it will return the object that was created before. S3 = Singleton() is not guaranteed, so the get_instance() method must be used when creating a class. Again, this can be problematic when using multiple threads, so let’s look at the following code:

class Singleton(object) :
    def __init__(self, *args, **kwargs) :
        import time
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs) :
        The # hasattr() function is used to determine whether an object has an attribute. In this case, the class has an _instance attribute
        if not hasattr(Singleton, '_instance'):
            Singleton._instance = Singleton(*args, **kwargs)

        return Singleton._instance


def task() :
    obj = Singleton.get_instance()
    print(obj)


for i in range(10):
    t = threading.Thread(target=task)
    t.start()
Copy the code

After executing the program, print the result:

<__main__.Singleton object at 0x031014B0>
<__main__.Singleton object at 0x00DA32F0>
<__main__.Singleton object at 0x03101430>
<__main__.Singleton object at 0x03101530>
<__main__.Singleton object at 0x031015B0>
<__main__.Singleton object at 0x031016B0>
<__main__.Singleton object at 0x03101630>
<__main__.Singleton object at 0x03101830>
<__main__.Singleton object at 0x03101730>
<__main__.Singleton object at 0x031017B0>

Process finished with exit code 0
Copy the code

If the __init__() method has some IO operations (simulated here using time.sleep(1)), it will find that the instance object is not the same. This is because the _instance property is obtained during the creation of an object. By the time they decide, no object has been instantiated yet, so they call init() to instantiate it, resulting in multiple calls and multiple objects being created. So how do you solve that? And the answer is lock it, lock it when you get the object property _instance, if someone else is already getting the object, if someone else wants to get the object, wait, because the person in front of you is probably creating the object, but it’s not done yet. The code is as follows:

class Singleton(object) :
    _instance_lock = threading.Lock()  # thread lock
    
    def __init__(self, *args, **kwargs) :
        import time
        time.sleep(1)

    @classmethod
    def get_instance(cls, *args, **kwargs) :
        with Singleton._instance_lock:
            The # hasattr() function is used to determine whether an object has an attribute. In this case, the class has an _instance attribute
            if not hasattr(Singleton, '_instance'):
                Singleton._instance = Singleton(*args, **kwargs)

            return Singleton._instance
Copy the code

However, in order to ensure thread safety, adding locking mechanism inside the class will make the locking part of the code serial execution, speed down.

Use the __new__() function

class Singleton(object) :

    def __init__(self) :
        print( "__init__" )

    def __new__(cls, *args, **kwargs) :
        print( "__new__" )
        if not hasattr(Singleton, "_instance") :print( "Create a new instance" )
            Singleton._instance = object.__new__(cls)
        return Singleton._instance


obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
Copy the code

When Python instantiates an object, it executes the class’s __new__() method first. When we do not write __new__(), we default to calling the base object’s __new__() method, and then execute the class’s __init__() method to initialize the object. So we can use this method to initialize the object. To implement the Singleton pattern, we use hasattr(Singleton, **”_instance”**) (hasattr() is used to determine whether an object hasa specified attribute) to determine whether the object has been instantiated before.

Attach console output: As you can see, singletons are also implemented.

The __init__() method is executed twice. The __init__() method is initialized twice.

class Singleton(object) :

    def __init__(self) :
        if not hasattr(Singleton, "_first_init") :print("__init__")
            Singleton._first_init = True

    def __new__(cls, *args, **kwargs) :
        print("__new__")
        if not hasattr(Singleton, "_instance") :print("Create a new instance")
            Singleton._instance = object.__new__(cls)
        return Singleton._instance


obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
Copy the code

and__new__()Method is to support multi-threading, do not need to add a separate thread lock to avoid the operation, save time and effort, nice.

conclusion

This article is about singletons, but it uses a lot of advanced Python syntax to implement singletons, including decorators, the magic function __new__(), the with block, and so on. If you’re interested in Python, you can always do more with less code.

Finally, thank my girlfriend for her tolerance, understanding and support in work and life!