我们首先理解一下什么叫做单例模式。

我们设想一种场景,我有一张银行卡,可能是我来取钱,也可能是我老爹偶尔善心大发给我发点赈灾款,但是毫无疑问,钱和我持有的银行卡相关。不论老爹是何时何地想起我已经穷得只能吃干脆面,还是我突然想起来还有一个老爹拨款专用小金库,我只要找到一个网点,插上我的卡,我就能得到money。

 

实际上python里面的单例模式就是为这样的一种场景考虑:如果你只想持有一张银行卡,并在银行系统支持的任何网点里面存取钱,都只能指向你的账户。也就是说,系统环境里面有一个类,无论在系统的什么地方,只要想实例化,就只能得到一个实例,实例化后对实例的任何操作,也只和第一个实例相关。

另外一个层面,单例模式同样避免了重复实例化的内存浪费,如果使用得当,能够大大节约系统资源。

 

下面就说一下python里面实现实例的几种方法,里面有几个和我之前讲过的知识点相关,这里mark一下:

  • 使用模块
  • 使用魔法方法 __new__
  • 使用装饰器
  • 使用元类

首先是模块方法实施单例模式,这个方法很简单,很土,但是很好理解,我这边玩花哨一点,重新实现一下类的两个魔法方法:__str__, __repr__

 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 import datetime
 8 
 9 
10 class ModuleSingleton(object):
11 
12     def __init__(self):
13         self.init_time = str(datetime.datetime.now())
14 
15     def gatherAttrs(self):
16         return ",".join("{}={}".format(k, getattr(self, k))
17                         for k in self.__dict__.keys())
18 
19     def __str__(self):
20         return "[{}:{}]".format(self.__class__.__name__, self.gatherAttrs())
21 
22     __repr__ = __str__
23 
24 myinstance = ModuleSingleton()

然后是两个模块分别调用它:

 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 from worker.singleton import test_module_singleton
 8 
 9 
10 
11 def get_first_instance():
12     return test_module_singleton.myinstance
 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 from worker.singleton import test_module_singleton
 8 
 9 
10 
11 def get_second_instance():
12     return test_module_singleton.myinstance

最后是调用他们的地方,并且打印这两个类:

 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 from worker.singleton import test_module1
 8 from worker.singleton import test_module2
 9 
10 
11 def main():
12     obj1 = test_module1.get_first_instance()
13     obj2 = test_module2.get_second_instance()
14     print obj1, obj2
15     print id(obj1), id(obj2)
16     
17 
18 if __name__ == '__main__':
19     main()

看看这个main函数跑完之后的打印:

1 pydev debugger: starting (pid: 16531)
2 [ModuleSingleton:init_time=2017-12-25 20:21:39.690618] [ModuleSingleton:init_time=2017-12-25 20:21:39.690618]
3 140711470863632 140711470863632

发现他们的初始化时间,以及实例的id,完全一模一样。这个很好理解,就像是把同一张银行卡从张江换到漕河泾插入一样(仿佛在暗示什么),归根结底还是同一张卡。

所以接下来是魔法方法__new__,这个方法比较抽象,可能要事先了解一下诸如super初始化规则,还有类变量,还有魔法方法__dict__。我之前介绍过和__dict__功能上很像的内置函数dir(),__dict__展示的是一个类的属性,像之前说过的__name__,__doc__也算是属性,当然咯,我们要用__new__实施单例模式,我们得用一个自定义的类变量,并根据该变量进行判断。

这边提一句,__init__方法是一个实例方法,什么叫实例方法,就是得实例化后才能执行的方法,通常参数列表包括关键字self。__init__是实例化后执行的第一个方法,它是自动执行的,用户侧看来,__init__好像完成了类实例化后的所有初始化动作。

 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 import datetime
 8 
 9 
10 class MagicNewSingleton(object):
11 
12     _instance = None
13     def __new__(cls, *args, **kw):
14         if not cls._instance:
15             cls._instance = super(MagicNewSingleton, cls).__new__(cls, *args, **kw)  
16         return cls._instance
17 
18     def __init__(self):
19         self.init_time = str(datetime.datetime.now())
20 
21     def gatherAttrs(self):
22         return ",".join("{}={}".format(k, getattr(self, k))
23                         for k in self.__dict__.keys())
24 
25     def __str__(self):
26         return "[{}:{}]".format(self.__class__.__name__, self.gatherAttrs())
27 
28     __repr__ = __str__
29 
30 
31 if __name__ == '__main__':
32     obj1 = MagicNewSingleton()
33     obj2 = MagicNewSingleton()
34     print obj1, obj2
35     print id(obj1), id(obj2)

接下来当然是它的打印:

1 [MagicNewSingleton:init_time=2017-12-25 20:40:33.918990] [MagicNewSingleton:init_time=2017-12-25 20:40:33.918990]
2 139931646473232 139931646473232

第三个就是和我之前讲过的装饰器相关,这个要玩得巧妙,而且要对装饰器、闭包了解比较深刻才能搞,我之前提过装饰器,实际上就是把被装饰的函数以及入参一起作为装饰器函数的参数执行一下,当然了,被装饰的未必是函数,类也可以被装饰。

 1 '''
 2 Created on Dec 25, 2017
 3 
 4 @author: kiel
 5 '''
 6 
 7 import datetime
 8 from functools import wraps
 9 
10 
11 def decorator_singleton(cls):
12     instances = {}
13     @wraps(cls)
14     def getinstance(*args, **kw):
15         if cls not in instances:
16             instances[cls] = cls(*args, **kw)
17         return instances[cls]
18     return getinstance
19 
20 
21 @decorator_singleton
22 class DecoratorSingleton(object):
23 
24     def __init__(self):
25         self.init_time = str(datetime.datetime.now())
26 
27     def gatherAttrs(self):
28         return ",".join("{}={}".format(k, getattr(self, k))
29                         for k in self.__dict__.keys())
30 
31     def __str__(self):
32         return "[{}:{}]".format(self.__class__.__name__, self.gatherAttrs())
33 
34     __repr__ = __str__
35 
36 
37 if __name__ == '__main__':
38     obj1 = DecoratorSingleton()
39     obj2 = DecoratorSingleton()
40     print obj1, obj2
41     print id(obj1), id(obj2)

它的打印是:

1 [DecoratorSingleton:init_time=2017-12-25 20:51:50.268966] [DecoratorSingleton:init_time=2017-12-25 20:51:50.268966]
2 140602111509648 140602111509648

需要在这个地方提一下,如果你对动态绑定感兴趣,希望在类实例化之后对它的属性进行动态修改绑定,实际上在装饰器模式下会遇到困难,装饰器函数可能没有类的属性,它无法修改,也就是说,这个装饰器方法实施后,不支持实例变量的动态绑定。

另外一个就是闭包,对于闭包要事先了解一下,否则难以理解instances = {}是如何存变量的。

 

最后一个就是元类了,我理解元类是基于C++的抽象类,实际上这个玩意很好理解,就是实现了一些抽象方法和属性,必须继承后完全重载后才能使得子类能够实例化,只要有一个抽象方法没有重新实现,就不行。这边我不会讲元类实现单例模式,稍后在元类的相关文章里面重新讲这块的内容。

不过,我可以在这里提一下魔法方法__call__,可以理解为 (),实例化需要 (),函数调用需要 (),只有实现了魔法方法__call__, 才能实施调用。元类实施单例模式就是依靠该方法实现的,类在实例化的过程中会层级向上调用__call__方法完成类的实例化,如果我们在类的元类里面重新实现__call__(这个是附注: 它的思路和__new__几乎一模一样),也就能实现单例模式。

 

那么总结一下:

  • 模块方法,只需要在一个模块里面完成实例化,在其他模块使用时直接导入即可。(详细的,请先理解模块的概念,包括locals(), globals()两个方法)。
  • __new__方法,重新实现类的'构造函数',使用类变量辨识类是否完成了初始化。
  • 装饰器方法,使用闭包变量来辨识类是否完成了初始化。
  • 元类方法, 非常复杂,需要依靠__call__方法和元类的概念,才能理解。