Singleton pattern is a common development and design pattern , Its main purpose is to ensure that only one instance object exists , That is to say, when you want a class to have a single function , You only need an instance object to complete when needed , You can use singleton mode , To save memory resources .
Let's say we have web Under development , One of the functions we often need to do is to use singleton mode to develop SMS captcha . We usually use communication products to send SMS captcha , Then we only need to use an instance object to complete the function of sending SMS .
1. Module implements singleton mode
Everyone should know , Module import can only make the imported program execute once , If you import multiple times, you will only execute once , So we can say that a module is a natural singleton pattern , therefore , We just need to define the relevant functions and data in a module , When you use it, import it to other modules to achieve the effect of singleton mode .
single.py
···
class Singleton(object):
def foo(self):
pass
print(1)
singleton = Singleton()
from singleton import singleton
from singleton import singleton
Execution results :
1
2. Using decorators to implement singleton patterns
We all know the function of decorators , We can add a function to other functions or classes , So we can also write a logic to the class , Let the class generate only one instance object .
def Singleton(cls):
_instance = {}
def singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]
return singleton
@Singleton
class A(object):
a = 1
def __init__(self, x=0):
self.x = x
a1 = A(2)
a2 = A(3)
print(a1)
print(a2)
Execution results :
<__main__.A object at 0x0000027432DABE48>
<__main__.A object at 0x0000027432DABE48>
3. Using class methods to implement singleton patterns
When we use class to create instance objects directly , It's not a singleton created , Then we need to define a class method in the class to edit the logic and implement the singleton pattern , The main idea is to judge whether a class has _instance This attribute , If so, this instance is returned directly , If not, create an instance .
class Singleton(object):
def __init__(self,*args,**kwargs):
pass
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1)
print(s2)
Execution results :
<__main__.Singleton object at 0x0000017B0359B550>
<__main__.Singleton object at 0x0000017B0359B550>
However, there is a hidden danger in implementing singleton mode in this way , When we add multithreading to create instance objects , We are fast enough to have no impact , When the execution speed is not fast enough , A thread creates an instance and gets _instance This property is used to judge , Other threads might get this at the same time _instance This attribute , It is found that no instance exists , So these two threads will create an instance at the same time , It will cause the phenomenon that multiple instances are created , Our singleton mode naturally fails . Give an example of , Let's add this break time here to demonstrate the slow execution .
import threading
import time
class Singleton(object):
def __init__(self, *args, **kwargs):
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task(arg):
obj = Singleton.get_instance(arg)
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
Execution results :
<__main__.Singleton object at 0x000001774942A518>
<__main__.Singleton object at 0x00000177495FA160>
<__main__.Singleton object at 0x00000177495F1EF0>
<__main__.Singleton object at 0x00000177495E5940>
<__main__.Singleton object at 0x00000177495AC0F0>
<__main__.Singleton object at 0x00000177495E58D0>
<__main__.Singleton object at 0x00000177495A6FD0>
<__main__.Singleton object at 0x00000177495F1D68>
<__main__.Singleton object at 0x00000177496060B8>
<__main__.Singleton object at 0x0000017749606240>
Now we can see that the instance object has changed to a different one ip 了 ? This is the reason we just mentioned . So how can we solve this problem ? Here we can achieve the effect by adding thread lock . After we've added thread locks , Here it is _instance Add this lock , Let each county run out before releasing it , Then the next thread can get it and continue running the program , It will not create the situation that different instances are created directly by getting the attribute value at the same time .
import threading
import time
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self, *args, **kwargs):
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
with Singleton._instance_lock:
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task(arg):
obj = Singleton.get_instance(arg)
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
Execution results :
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
<__main__.Singleton object at 0x000001EC8FAB9550>
4. Use __new__ Method to implement the singleton pattern
We should all know that our instance object is through __new__ Method , If we override class __new__ Method , Can we directly rewrite the case where multiple instances can be created to create only one instance object ? The answer is yes , We can rewrite __new__ Method time , Add judgment if there is an instance object , Then directly return the created object , So we can achieve this effect .
import threading
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self, *args, **kwargs):
pass
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with Singleton._instance_lock:
if not hasattr(cls, '_instance'):
Singleton._instance = super().__new__(cls)
return Singleton._instance
def task(arg):
obj = Singleton()
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
Running results :
<__main__.Singleton object at 0x00000111C88B9588>
None
None
None
None
None
None
None
None
None
5. Use metaclass metaclass Implement singleton mode
Python The metaclass of is the class that controls the creation of the class , In that case , Then we can create our class through metaclass when creating class , It can only generate one instance object ?
import threading
class SingletonType(type):
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
with SingletonType._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
return cls._instance
class Foo(metaclass=SingletonType):
def __init__(self,name):
self.name = name
obj1 = Foo('name')
obj2 = Foo('name')
print(id(obj1))
print(id(obj2))
Execution results :
<__main__.Foo object at 0x000001E97FC14400>
<__main__.Foo object at 0x000001E97FC14400>
Okay , So here are some ways to create singleton patterns , in general , It doesn't matter whether it's useful or not , During the interview , We pretend x It still works , you get Have we arrived ?