单例模式(Singleton Pattern)是一种常用的开发设计模式,它的主要目的就是确保只有一个实例对象的存在。换句话说,当一个类的功能比较单一,只需要一个实例对象就可以完成需求的时,就可以使用单例模式来节省内存资源。
1. 模块实现单例
python模块在第一导入时,会生成.pyc编译文件,当再次导入时,就直接加载.pyc编译文件,而不会再次执行模块代码,所以可以说模块就是一个天然的单例模式。利用模块这个特性,只需把相关函数和数据定义在一个模块中,将它导入到其他模块使用就可以达到单例模式的效果。
# person.py
class Person(object):
def __init__(self,name, age):
self.name = name
self.age = age
person_instance = Person('张三',18)
# test.py
from person import person_instance
if __name__ == '__main__':
p1 = person_instance
p2 = person_instance
p2.name = '李四'
p2.age = 22
print(p1)
print(p2)
print(p1 == p2)
# 输出结果
True
2. 装饰器实现单例
装饰器的作用是给函数或者类扩展功能,所以可以利用装饰器来让类只生成一个实例对象。在装饰器里定义一个字典,用来存放类的实例。当第一次创建时,将这个实例保存到这个字典中。然后以后每次创建对象的时候,都去这个字典中查找是否已经存在。如果存在,就直接返回这个实例对象,否则,就创建实例并保存到字典中。使用函数的方式创建装饰器
# 示例代码
def singleton(cls):
_instance = {}
def decorated(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return decorated
@singleton
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
p1 = Person('张三',18)
p2 = Person('李四',22)
print(p1)
print(p2)
print(p1==p2)
# 输出结果
True使用类的方式创建装饰器
# 示例代码
class Singleton(object):
def __init__(self,cls):
self._cls = cls
self._instance = {}
def __call__(self, *args):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls(*args)
return self._instance[self._cls]
@Singleton
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
p1 = Person('张三',18)
p2 = Person('李四',22)
print(p1)
print(p2)
print(p1==p2)
# 输出结果
True
3. 使用类方法实现单例
当使用类直接创建实例对象的时候,创建的并不是单例对象,那么可以在类中定义一个类方法来实现单例模式,主要思路就是会去判断类是否有_instance这个属性,如果有则直接返回这个实例,没有则创建实例。
# 示例代码
class Person(object):
def __init__(self,name, age):
self.name = name
self.age = age
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = cls(*args, **kwargs)
return cls._instance
if __name__ == '__main__':
p1 = Person.get_instance('张三',18)
p2 = Person.get_instance('李四',22)
print(p1)
print(p2)
print(p1 == p2)
# 输出结果
True
4. 使用__new__方法实现单例
Python中一个对象实例化过程大致为:首先执行类的__new__方法,如果没有,默认会调用父类的__new__方法,返回一个实例化对象。然后在调用__init__方法对此对象进行初始化。我们可以利用这个过程,实现单例模式。在类的__new__方法中,判断是否存在实例,如果存在,直接返回实例,否则,创建一个实例。这样就能够达到单例模式效果了。
# 示例代码
class Person(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,name, age):
self.name = name
self.age = age
if __name__ == '__main__':
p1 = Person('张三',18)
p2 = Person('李四',22)
print(p1)
print(p2)
print(p1 == p2)
# 输出结果
True
5. 使用元类metaclass实现单例
Python中元类是控制类创建的类,所以可以在创建类的时候通过元类来创建类,在元类的__call__方法中,判断是否存在实例,如果存在,直接返回实例,否则,创建一个实例,使其只存在一个实例对象。
# 示例代码
class Singleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Person(metaclass=Singleton):
def __init__(self,name, age):
self.name = name
self.age = age
if __name__ == '__main__':
p1 = Person('张三',18)
p2 = Person('李四',22)
print(p1)
print(p2)
print(p1 == p2)
# 输出结果
True
6. 存在的问题
在使用类方法、__new__方法、元类来实现单例时,在单线程下是安全的。但是如果遇到多个线程同时创建这个类的实例的时候就会出现问题。多线程下存在同时创建多个实例的现象,属于线程安全问题
当加入多线程去创建实例对象的时候,如果执行速度够快还不会出现影响,当执行速度不够快的时候,一个线程去创建实例然后拿到了_instance这个属性去判断,其他的线程也可能会拿到_instance这个属性,发现并没有实例存在。此时,这两个线程就会同时创建一个实例,造成同时创建多个实例的现象,那么单例模式自然也就失效了。
# 示例代码
import threading
import time
class Person(object):
def __init__(self,name, age):
self.name = name
self.age = age
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = cls(*args, **kwargs)
return cls._instance
def task(arg):
p = Person.get_instance('张三',18)
print(p)
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=task, args=[i,])
t.start()
# 输出结果
<__main__.person object at>解决办法是加线程锁
在获取对象属性_instance的时候加锁,让每一个线程使用完后才释放它,接着下一个线程继续运行程序,就可以解决同时获取对象并创建不同的实例的问题。
# 示例代码-类方法实现单例(改进版)
import threading
import time
class Person(object):
_lock = threading.Lock()
def __init__(self,name, age):
self.name = name
self.age = age
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
with cls._lock:
if not hasattr(cls, '_instance'):
cls._instance = cls(*args, **kwargs)
return cls._instance
def task(arg):
p = Person.get_instance('张三',18)
print(p)
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=task, args=[i,])
t.start()
# 输出结果
# 示例代码__new__方法实现单例(改进版)
# 示例代码-__new__方法实现单例(改进版)
import threading
import time
class Person(object):
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
time.sleep(1)
with cls._lock:
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self,name, age):
self.name = name
self.age = age
def task(arg):
p = Person('张三',18)
print(p)
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=task, args=[i,])
t.start()
# 示例代码-元类实现单例(改进版)
# 示例代码-元类实现单例(改进版)
import threading
import time
class Singleton(type):
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
time.sleep(1)
with cls._lock:
if not hasattr(cls, "_instance"):
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Person(metaclass=Singleton):
def __init__(self,name, age):
self.name = name
self.age = age
def task(arg):
p = Person('张三',18)
print(p)
if __name__ == '__main__':
for i in range(10):
t = threading.Thread(target=task, args=[i,])
t.start()
以上是整理的Python中实现单例模式的几种方式,希望对大家学习Python有所帮助。
持续创作需要您的支持,拜托了~~~
如果能帮我关注公众号【不灵兔】那就再好不过了知乎视频www.zhihu.com