单例模式的使用场景
- Python的logger就是一个单例模式,用以日志记录
- Windows的资源管理器是一个单例模式
- 线程池,数据库连接池等资源池一般也用单例模式
- 网站计数器
从这些使用场景我们可以总结下什么情况下需要单例模式:
- 当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;
- 当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。
当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。
Python来实现一个单例模式
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs): # 这里不能使用__init__,因为__init__是在instance已经生成以后才去调用的
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
a = Singleton()
b = Singleton()
print(a)
print(b)
print(id(a))
print(id(b))
打印结果如下:
<__main__.singleton object at 0x017EEFF0>
<__main__.singleton object at 0x017EEFF0>
25096176
25096176
可以看出两次创建对象,结果返回的是同一个对象实例,我们再让我们的例子更接近真实的使用场景来看看
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls)
return cls.__instance
def __init__(self, status_number):
self.status_number = status_number
s1 = Singleton(2)
s2 = Singleton(5)
print s1
print s2
print s1.status_number
print s2.status_number
这里我们使用了init方法,下面是打印结果,可以看出确实是只有一个实例,共享了实例的变量
<__main__.Singleton object at 0x7f5116865490>
<__main__.Singleton object at 0x7f5116865490>
5
5
这个例子中有一个问题我们没有解决,那就是多线程的问题,当有多个线程同时去初始化对象时,就很可能同时判断__instance is None,从而进入初始化instance的代码中。所以为了解决这个问题,我们必须通过同步锁来解决这个问题。以下例子来自xiaorui
import threading
try:
from synchronize import make_synchronized
except ImportError:
def make_synchronized(func):
import threading
func.__lock__ = threading.Lock()
def synced_func(*args, **kws):
with func.__lock__:
return func(*args, **kws)
return synced_func
class Singleton(object):
instance = None
@make_synchronized
def __new__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = object.__new__(cls, *args, **kwargs)
return cls.instance
def __init__(self):
self.blog = "xiaorui.cc"
def go(self):
pass
def worker():
e = Singleton()
print(id(e))
e.go()
def test():
e1 = Singleton()
e2 = Singleton()
e1.blog = 123
print(e1.blog)
print(e2.blog)
print(id(e1))
print(id(e2))
if __name__ == "__main__":
test()
task = []
for one in range(30):
t = threading.Thread(target=worker)
task.append(t)
for one in task:
one.start()
for one in task:
one.join()
更简单地使用单例模式
在Python的官方网站给了两个例子是用装饰符来修饰类,从而使得类变成了单例模式,使得我们可以通过更加简单的方式去实现单例模式
官网Singleton
第一种装饰器写法(简单)
def singleton (cls, *args, **kwargs):
instances = {}
def get_instance (*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
代码分析:
第1行,创建外层函数singleton,可以传入类
第3行,创建一个instances字典用来保存单例
第5行,创建一个内层函数来获得单例
第7,9,11行, 判断instances字典中是否含有单例,如果没有就创建单例并保存到instances字典中,然后返回该单例
第13行, 返回内层函数get_instance
@singleton
class Student(object):
def __init__(self, name, age):
self.name = name
self.age = age
def run(self):
print(self.name)
if __name__ == '__main__':
student = Student('wang', 18)
student.run()
hshs = Student('kai', 21)
hshs.run()
执行结果:
wang
wang
student = Student('wang', 18) 即相当于直接把 Student('wang', 18) 作用 singleton的第一个参数传入方法中,此时instance 为空,当这个类第一次实例化之后 instalce = {<class 'main.Student'>:<main.Student object at 0x0000026CFBA5CF60>}
第二种装饰器写法(复杂)
import functools
def singleton(cls):
"""
将一个类作为单例
"""
cls.__new_original__ = cls.__new__
@functools.wraps(cls.__new__)
def singleton_new(cls, *args, **kw):
it = cls.__dict__.get('__it__')
if it is not None:
return it
cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
it.__init_original__(*args, **kw)
return it
cls.__new__ = singleton_new
cls.__init_original__ = cls.__init__
cls.__init__ = object.__init__
return cls
@ singleton
class Foo:
def __new__(cls):
cls.x = 10
return object.__new__(cls)
def __init__(self):
assert self.x == 10
self.x = 15
assert Foo().x == 15
Foo().x = 20
assert Foo().x == 20