单例模式 Singleton
网上搜索python 单例模式,实现方式大致有一下几种方式:
- 以模块导入的方式
- 使用
__new__
- 使用 装饰器(decorator)
- 使用 元类 (metaclass)
- 使用 面向对象的常用方式(类方法)
- 使用 变量名覆盖
推荐方式:
以《剑指offer》的Singleton的实现顺序来讲解,直接上我认为python实现单例模式的最佳方式 即 网传的 __new__
方式,直接上代码:
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs):
if not Singleton.__instance:
Singleton.__instance = super().__new__(cls)
return Singleton.__instance
#测试
for i in range(5):
s = Singleton()
print(s)
输出:
<__main__.Singleton object at 0x000000000B685278>
<__main__.Singleton object at 0x000000000B685278>
<__main__.Singleton object at 0x000000000B685278>
<__main__.Singleton object at 0x000000000B685278>
<__main__.Singleton object at 0x000000000B685278>
可以看到5次实例化的 Singleton
对象均为同一个变量,但是该方法只适用于单线程模式,多线程模式下多个线程可能同时进入 if not Singleton.__instance:
中 此时就会返回不同的 Singleton
对象,测试:
#测试:
import time
import threading
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwargs):
if not Singleton.__instance:
time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
Singleton.__instance = super().__new__(cls)
return Singleton.__instance
def test():
s = Singleton()
print('------%s\n' % s)
def main():
t = {}
for i in range(5):
t[i] = threading.Thread(target=test)
t[i].start()
for i in range(5):
t[i].join()
main()
输出:
------<__main__.Singleton object at 0x000000000B789080>
------<__main__.Singleton object at 0x000000000B7895C0>
------<__main__.Singleton object at 0x000000000B7896A0>
------<__main__.Singleton object at 0x000000000B645F60>
------<__main__.Singleton object at 0x000000000B7896A0>
可见5次实例化的 Singleton
对象确实在多线程的情况下并没有实现正真的单例情况。那怎么办,自然有方法啦,继续按照《剑指offer》的思路走,在线程要抢占的共享资源处上锁。如下:
class Singleton(object):
__lock = threading.Lock() #生成一个锁对象
__instance = None
def __new__(cls, *args, **kwargs):
Singleton.__lock.acquire()
if not Singleton.__instance:
time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
Singleton.__instance = super().__new__(cls)
Singleton.__lock.release()
return Singleton.__instance
#测试
输出:
------<__main__.Singleton object at 0x000000000B645AC8>
------<__main__.Singleton object at 0x000000000B645AC8>
------<__main__.Singleton object at 0x000000000B645AC8>
------<__main__.Singleton object at 0x000000000B645AC8>
------<__main__.Singleton object at 0x000000000B645AC8>
如上,多线程情况下,每一次获取的 Singleton
对象都是同一变量,此处注意__lock
一定要在类中唯一,否则下面的上锁与释放锁过程会失效,或者出错。按照网上以及《剑指offer》的流程还要继续优化,即:Singleton.__lock.acquire()
上锁,释放锁的过程会耗时比较严重,所以需要在上锁之前再做判断,如下:
...
class Singleton(object):
__lock = threading.Lock()
__instance = None
def __new__(cls, *args, **kwargs):
if not Singleton.__instance:
Singleton.__lock.acquire()
if not Singleton.__instance:
time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
Singleton.__instance = super().__new__(cls)
Singleton.__lock.release()
return Singleton.__instance
...
这样只有第一个线程会进入if
中,即只有一个线程会有 上锁及释放锁
的过程,以后的线程会直接返回现有的__instance
实例。由于python 的面向对象机制和其他语言java C# 等不同,所以《剑指offer》的后面俩种方法在python中实现可能比较困难,至此结束python的第一种实现Singleton
的方法。但是经过我的实际测试,python中上锁,释放锁
的过程并没有很耗时,很是不解,如果有哪位朋友清楚还请不吝赐教,感谢!!测试如下:
import time
import threading
class Singleton(object):
__lock = threading.Lock()
__instance = None
def __new__(cls, *args, **kwargs):
if not Singleton.__instance:
Singleton.__lock.acquire()
if not Singleton.__instance:
time.sleep(2) #为了模仿有大量线程运行时,多个线程可能同时进入,特此暂定2秒钟
Singleton.__instance = super().__new__(cls)
Singleton.__lock.release()
return Singleton.__instance
def test():
s = Singleton()
print('------%s\n' % s)
def main():
start = time.time()
t = {}
for i in range(5):
t[i] = threading.Thread(target=test)
t[i].start()
for i in range(5):
t[i].join()
end = time.time()
print(end-start)
main()
#测试:
输出:
......
2.285130739212036
测试 未加 if 判断的情况,耗时为:
2.279130220413208
对俩种实现方式进行耗时测试,发现:做了if not Singleton.__instance:
判断的实现方式 和 没做判断的实现方式 相比耗时并没有差太多,甚至前者比后者更耗时 ,(在线程说为1000的情况下测试结果一样)我就真的懵逼了!!!所以还是推荐第一种多线程下的实现方式。
-
优点: 实例化对象的方式
singleton= Singleton()
子类直接继承也是单例 -
缺点: 暂时没想到
__new__()
方法过于强大,不建议使用 ^ _ ^ 哈
以模块导入方式:
其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了,上代码:
在当前目录下新建 singleton_moudle.py:
class Singleton(object):
#字段...
data = None
# 方法
def hh(self):
pass
singleton = Singleton()
......
然后在当前文件中,将 singleton 导入即是一个单例
在 test.py 中
from singleton_moudle import singleton
print(singleton)
优点:实现方式简单,而且也是天然的多线程
缺点:耦合性较强,在导入模块的时候就生成占用空间,且不能实现单例的继承
使用 装饰器 (decorator)
import threading
def singleton(cls):
__singleton = {}
def mySingleton(*args, **kwargs):
if cls not in __singleton:
__singleton[cls] = cls(*args, **kwargs)
return __singleton[cls]
return mySingleton
@singleton
class A(object):
data = None
def hh(self):
print('ggggggg')
该方法可谓是绝妙啊。简直就是一个单例库:
优点:可以将任意的类装饰为单例类,方便
缺点:也是适用于单线程,当然可以通过加锁实现多线程,也是种极推荐的方法
使用 面向对象的常用方式:
@staticmethod
、@classmethod
class Singleton(object):
__instance = None
__lock = threading.Lock()
def __init__(self):
raise SyntaxError('can not instance, please use get_instance')
@classmethod
def get_instance(cls):
Singleton.__lock.acquire()
if not Singleton.__instance:
time.sleep(2)
Singleton.__instance = object.__new__(cls)
Singleton.__lock.release()
return Singleton.__instance
t = []
def test():
singleton = Singleton.get_instance()
print(str(singleton)+'\r\n')
print('------------------start')
for i in range(5):
t.append(threading.Thread(target=test))
t[i].start()
for i in t:
i.join()
print('------------------end')
我们在类的__init__
方法中手动抛出异常,说明不能使用常规的方式来生成实例,而是使用了,类方法,或者静态方法get_instance
来获取一个实例。使用@staticmethod
@classmethod
类似于java中实现单例模式。此处只实现了用@classmethod
装饰的方法,@staticmethod
方法类似。可以查看参考文件 python中7种实现单例模式的方法查看具体代码
- 优点:使用和其他面向对象语言类似的方式,容易接受
-
缺点:实际是有风险的,
s = Singleton()
会报错,但实际s确实是一个 Singleton的实例,不是严格意义上的单例
使用 变量名覆盖的方式:
即对象覆盖类,并重写__call__方法
class Singleton(object):
def hh(self):
print('hhh')
def __call__(self):
return self
Singleton = Singleton()
s1 = Singleton()
s2 = Singleton()
print(s1)
print(s2)
先创建一个Singleton的实例对象
,将该对象赋值给Singleton变量
,此时Singleton不再指向类了,而是指向了一个具体的实例对象;然后当调用 Singleton()
时,因为重写了 对象的 __call__
方法,所以直接返回对象本身;该方法也是很独特,虽然很简单,但是不推荐
- 优点: 实现简单,天然的多线程
- 缺点: 分层不明显,不能继承,且对象像是从石头缝里蹦出来的,很奇诡
讲这种方法,并不希望大家这样这样实现单例,而是这种思维也是很有趣的,想突出以下,可见python的灵活性,此处有个知识点:python 中一切皆对象
一个对象是否可以被像函数一样执行,要看它是否实现了__call__
即只要给一个对象实现了__call__方法,那么这个对象就是可被执行的。
其实类来实例化一个对象也是调用了类的__call__
方法;特别注意此处是修改了对象的__call__
方法而非 类的__call__
方法,所以第一步Singleton= Singleton()
真实的返回了一个Singleton对象的实例,但是再次调用a=Singleton()
时,此处调用了对象的__call__
方法直接返回对象本身。从而实现了单例,如果不懂请看我的另一篇文章:《python 实例化对象的过程 详解》
使用 元类 (metaclass)
因为元类在python 比较复杂,且一般用的很少。此处我也不太想深究,有兴趣的同学可以自行google,或者查看我下面的参考资料,见谅!!
总结:
python 实现单例模式的方法有很多,但是推荐使用
__new__方式
装饰器方式
python 中上锁与释放锁的过程貌似不耗时(待定)
python 中一切皆对象,若对象实现了__call__方法 则可被调用执行
声明:
本人也是python 小白,如果上述内容有讲的不对的地方还请各位批评指点。将不胜感激,再次感谢~~~
参考资料:
- python中7种实现单例模式的方法
- 《剑指offer》面试题2:
-
Python中的单例模式的几种实现方式的及优化
`
网友评论