current position:Home>Python - singleton pattern of software design pattern

Python - singleton pattern of software design pattern

2022-02-01 17:14:55 Cheng Xuyuan Xiaozhuang

This is my participation 11 The fourth of the yuegengwen challenge 30 God , Check out the activity details :2021 One last more challenge

The singleton pattern

Singleton is a software design pattern , To ensure a class , No matter how many times the generated instance object is called , They all point to the same memory address , There's just one example ( There is only one object ).

Advantages of singleton mode

1、 Because the singleton mode requires only one instance in the global , Therefore, more memory space can be saved ; 2、 There is only one access point globally , Better data synchronization control , Avoid multiple occupancy ; 3、 Single instance long resident memory , Reduce system overhead .

Disadvantages of singleton mode

1、 It's difficult to extend the singleton pattern ; 2、 Given to a single example of the duties of Ethereum , To some extent, it violates the principle of single responsibility ; 3、 Singleton mode is the first one to be completed in concurrent collaboration software module , So it's not good for testing ; 4、 The singleton mode can lead to “ Resource bottleneck ”.

Examples of the application of singleton pattern

1、 Generating globally unique serial numbers ; 2、 Access the only resource that is globally reused , Disk 、 Bus etc. ; 3、 A single object takes up too many resources , Such as database, etc ; 4、 The whole system is under unified management , Such as Windows Under the Task Manager; 5、 Website counter .

Design patterns

Design pattern is a solution formed by refining and abstracting various problems . These designs are constantly tested by predecessors , Encapsulation is considered 、 reusability 、 efficiency 、 Modifiable 、 Highly summary of various factors such as portability . It is not limited to a specific language , It is an idea and method to solve problems

Add : Common design patterns , Design patterns have also derived many new types , Not limited to this 23 Kind of

Design patterns can be divided into three broad categories : Create class design patterns 、 Structural design patterns 、 Behavioral design patterns

Create class design patterns (5 Kind of )

The singleton pattern 、 Factory mode ( Simple factory model 、 Abstract factory pattern )、 Builder pattern 、 Archetypal model

Structural design patterns (7 Kind of )

The proxy pattern 、 Decorator mode 、 Adapter pattern 、 Facade mode 、 Portfolio model 、 The flyweight pattern 、 Bridge mode

Behavioral design patterns (11 Kind of )

The strategy pattern 、 The chain of responsibility model 、 Command mode 、 Intermediary model 、 Template pattern 、 Iterator pattern 、 Visitor mode 、 Observer mode 、 Interpreter mode 、 Memo mode 、 The state pattern

Implement singleton mode

There are many ways to implement the singleton pattern , But the general principle is to ensure that a class only instantiates an object , The next instance will return the object directly , No more instantiation operations . So the The key point is , How to determine whether this class has instantiated an object .

Here are two ways :

  • One is Import by module The way ;
  • One is By magic The way of judging ;
#  The basic principle :
-  The first type is through module import , It borrows the underlying principle of module import to realize .
-  When a module (py file ) When imported , First, I will execute the code of this module , Then load the module's namespace into memory .
-  When this module is imported for the second time , The file will no longer be executed , Instead, look directly in memory .
-  therefore , If the module is imported for the first time , When executing the file source code, a class is instantiated , When importing again , You won't instantiate .

-  The second type is mainly based on class and metaclass implementation , stay ' object ' To determine whether an object has been instantiated 
-  This kind of way , According to the different methods of implementation , It can be divided into different methods , Such as :
-  Through the binding method of the class ; Through the metaclass ; Through the... Under the class __new__; Through ornaments ( Function decorator , Class decorator ) To achieve, etc .
 Copy code 

The following describes these different implementation methods , For reference only, implementation ideas , No specific needs .

Import by module

# cls_singleton.py
class Foo(object):
    pass

instance = Foo()

# test.py
import cls_singleton

obj1 = cls_singleton.instance
obj2 = cls_singleton.instance
print(obj1 is obj2)

#  principle : The module imports the mechanism found from memory for the second time 
 Copy code 

Through the binding method of the class

class Student:
    _instance = None	#  Record the instantiated object 

    def __init__(self, name, age):
        self.name = name
        self.age = age

 @classmethod
    def get_singleton(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = cls(*args, **kwargs)
        return cls._instance

stu1 = Student.get_singleton('jack', 18)
stu2 = Student.get_singleton('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)

#  principle : Class binding method is the second way to instantiate objects ,
#  The first instantiated object is saved as the data attribute of the class  _instance,
#  When re instantiating for the second time , stay get_singleton It is judged that there is already an instance object , Directly return the data properties of the class  _instance
 Copy code 

Add : The singleton pattern implemented in this way has an obvious advantage bug;bug The root of is that if the user does not instantiate the object through the method of binding class , Instead, instantiate the object directly through the class name with parentheses , Then this is no longer a simple interest model .

By magic __new__

class Student:

    _instance = None

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        # if cls._instance:
        # return cls._instance #  If there is an instance, it will return to 
        # else:
        # cls._instance = super().__new__(cls) #  If there is no instance, then new One and save 
        # return cls._instance #  This return is for init, Instantiate again , It doesn't matter 

        if not cls._instance:	                        #  This is a simplified way of writing , The above notes are easier to put forward judgment ideas 
            cls._instance = super().__new__(cls)
        return cls._instance


stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)

#  principle : And methods 2 similar , Will judge the implementation of , Transfer from the binding method of the class to the binding method of the class __new__ in 
#  At the end of the day, it's all   Determine whether the class has an instance , Go straight back to , If none, instantiate and save to _instance in .
 Copy code 

Add : In this way, the singleton mode can be implemented almost perfectly , But it's still not perfect . The imperfection is that the extreme case of concurrency is not taken into account , It is possible that multiple threads instantiate objects at the same time . The supplementary contents on this point are introduced in the last section of this paper (!!! Advanced will ).

Through the metaclass

class Mymeta(type):

    def __init__(cls, name, bases, dic):
        super().__init__(name, bases, dic)
        cls._instance = None		                  #  The data attribute of the instance object of the record class is automatically defined in the metaclass 

    def __call__(cls, *args, **kwargs):	                  #  this call Will be called in the class ( That is, trigger when instantiating )
        if cls._instance:				  #  Judge whether the class has instantiated objects 
            return cls._instance
        else:						  #  When there is no instantiated object , The control class empties the object and initializes 
            obj = cls.__new__(cls, *args, **kwargs)
            obj.__init__(*args, **kwargs)
            cls._instance = obj			          #  Save the object , The next re instantiation can be returned directly without recreating the object 
            return obj
        
        	#  The above four lines of code can be abbreviated to the following two lines of code 
            # cls._instance = super().__call__(cls, *args, **kwargs)
            # return cls._instance


class Student(metaclass=Mymeta):
    def __init__(self, name, age):
        self.name = name
        self.age = age


stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)

#  principle : When the class is defined, the... Under the metaclass will be called __init__, Class call ( Instantiate objects ) Will trigger... Under the metaclass __call__ Method 
#  Class definition , Add an empty data attribute to the class ,
#  When first instantiated , After instantiation, the object is assigned to the data attribute of the class ; When re instantiating for the second time , Directly return the data property of the class 
#  And way 3 The difference 1: This data attribute of the class is automatically defined in the metaclass , Instead of the definition shown in the class .
#  And way 3 The difference 2: The metaclass is triggered when the class is called __call__ Method to determine whether there is an instantiated object , Instead of judging in the binding method of the class 
 Copy code 

Function decorator

def singleton(cls):
    _instance_dict = {}		                  #  Use dictionary , You can decorate multiple classes , Control multiple classes to implement singleton mode 
  
    def inner(*args, **kwargs):
        if cls not in _instance_dict:
            _instance_dict[cls] = cls(*args, **kwargs)
        return _instance_dict.get(cls)
    return inner


@singleton
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # def __new__(cls, *args, **kwargs): #  The method 3 This part of the code is moved to the function decorator 
    # if not cls._instance:
    # cls._instance = super().__new__(cls)
    # return cls._instan
    
stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)
 Copy code 

Class decorator

class SingleTon:
    _instance_dict = {}

    def __init__(self, cls_name):
        self.cls_name = cls_name

    def __call__(self, *args, **kwargs):
        if self.cls_name not in SingleTon._instance_dict:
            SingleTon._instance_dict[self.cls_name] = self.cls_name(*args, **kwargs)
        return SingleTon._instance_dict.get(self.cls_name)


@SingleTon #  This grammar is equivalent to sugar Student = SingleTon(Student), namely Student yes SingleTon Instance object of 
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

stu1 = Student('jack', 18)
stu2 = Student('jack', 18)
print(stu1 is stu2)
print(stu1.__dict__, stu2.__dict__)

#  principle : In the idea of function decorator , Encapsulate the decorator into a class .
#  When the program is executed with syntax sugar , Will instantiate a Student object , This object is SingleTon The object of .
#  It's used later Student What is essentially used is SingleTon The object of .
#  So use Student('jack', 18) To instantiate an object , It's actually calling SingleTon The object of , Will trigger it __call__ Implementation 
#  So it's in __call__ in , Judge Student Class has no instance object .
 Copy code 

!!! Advanced will

This part mainly introduces the multi thread concurrency , When multithreading is highly concurrent , If there are multiple threads at the same time ( In extreme conditions ) Instantiated objects , Then there will be multiple objects , This is no longer singleton mode .

Solve the competition problem caused by multithreading concurrency , The first thought is to add mutex , So we use the principle of mutual exclusion to solve this problem .

The key point to solve , Nothing more than adding a lock to the part of the specific exemplary operation , In this way, multiple threads coming at the same time need to queue .

In this way, only the first thread to grab the lock instantiates an object and saves it in _instance in , Other threads that grab the lock at the same time grab the lock again , Will not enter this judgment if not cls._instance, Save directly in _instance The object returned . In this way, the singleton mode under multithreading is realized .

There is another problem to be solved at this time , All subsequent instance objects need to be locked again , This will greatly reduce the efficiency of execution . It's easy to solve this problem , Directly before grabbing the lock , Determine whether there is a singleton object , If there is, we won't rob the lock down ( Code No. 11 Judge the meaning of existence ).

import threading

class Student:
    _instance = None				#  Save singleton objects 
    _lock = threading.RLock()		        #  lock 

    def __new__(cls, *args, **kwargs):
        if cls._instance:			#  If there is already a single case, don't grab the lock , avoid IO wait for 
            return cls._instance
        
        with cls._lock:				#  Use with grammar , Easy to grab and release the lock 
            if not cls._instance:	
                cls._instance = super().__new__(cls, *args, **kwargs)
            return cls._instance
 Copy code 

Conclusion

The article begins with the official account of WeChat Cheng Xuyuan Village , Synchronize on Nuggets .

It's not easy to code words , Reprint please explain the source , Let's go after passing by with our lovely little fingers (╹▽╹)

copyright notice
author[Cheng Xuyuan Xiaozhuang],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2022/02/202202011714507780.html

Random recommended