單例模式的實現方式
將類實例綁定到類變量上
1
2
3
4
5
6
7
|
class Singleton( object ): _instance = None def __new__( cls , * args): if not isinstance ( cls ._instance, cls ): cls ._instance = super (Singleton, cls ).__new__( cls , * args) return cls ._instance |
但是子類在繼承后可以重寫__new__以失去單例特性
1
2
3
4
|
class D(Singleton): def __new__( cls , * args): return super (D, cls ).__new__( cls , * args) |
使用裝飾器實現
1
2
3
4
5
6
7
8
9
10
11
12
|
def singleton(_cls): inst = {} def getinstance( * args, * * kwargs): if _cls not in inst: inst[_cls] = _cls( * args, * * kwargs) return inst[_cls] return getinstance @singleton class MyClass( object ): pass |
問題是這樣裝飾以后返回的不是類而是函數,當然你可以singleton里定義一個類來解決問題,但這樣就顯得很麻煩了
使用__metaclass__,這個方式最推薦
1
2
3
4
5
6
7
8
9
10
11
|
class Singleton( type ): _inst = {} def __call__( cls , * args, * * kwargs): if cls not in cls ._inst: cls ._inst[ cls ] = super (Singleton, cls ).__call__( * args) return cls ._inst[ cls ] class MyClass( object ): __metaclass__ = Singleton |
Tornado中的單例模式運用
來看看tornado.IOLoop中的單例模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class IOLoop( object ): @staticmethod def instance(): """Returns a global `IOLoop` instance. Most applications have a single, global `IOLoop` running on the main thread. Use this method to get this instance from another thread. To get the current thread's `IOLoop`, use `current()`. """ if not hasattr (IOLoop, "_instance" ): with IOLoop._instance_lock: if not hasattr (IOLoop, "_instance" ): # New instance after double check IOLoop._instance = IOLoop() return IOLoop._instance |
為什么這里要double check?來看個這里面簡單的單例模式,先來看看代碼:
1
2
3
4
5
6
7
|
class Singleton( object ): @staticmathod def instance(): if not hasattr (Singleton, '_instance' ): Singleton._instance = Singleton() return Singleton._instance |
在 Python 里,可以在真正的構造函數__new__里做文章:
1
2
3
4
5
6
|
class Singleton( object ): def __new__( cls , * args, * * kwargs): if not hasattr ( cls , '_instance' ): cls ._instance = super (Singleton, cls ).__new__( cls , * args, * * kwargs) return cls ._instance |
這種情況看似還不錯,但是不能保證在多線程的環境下仍然好用,看圖:
出現了多線程之后,這明顯就是行不通的。
1.上鎖使線程同步
上鎖后的代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
import threading class Singleton( object ): _instance_lock = threading.Lock() @staticmethod def instance(): with Singleton._instance_lock: if not hasattr (Singleton, '_instance' ): Singleton._instance = Singleton() return Singleton._instance |
這里確實是解決了多線程的情況,但是我們只有實例化的時候需要上鎖,其它時候Singleton._instance已經存在了,不需要鎖了,但是這時候其它要獲得Singleton實例的線程還是必須等待,鎖的存在明顯降低了效率,有性能損耗。
2.全局變量
在 Java/C++ 這些語言里還可以利用全局變量的方式解決上面那種加鎖(同步)帶來的問題:
1
2
3
4
5
6
7
8
9
10
11
|
class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } } |
在 Python 里就是這樣了:
1
2
3
4
5
6
7
8
9
10
|
class Singleton( object ): @staticmethod def instance(): return _g_singleton _g_singleton = Singleton() # def get_instance(): # return _g_singleton |
但是如果這個類所占的資源較多的話,還沒有用這個實例就已經存在了,是非常不劃算的,Python 代碼也略顯丑陋……
所以出現了像tornado.IOLoop.instance()那樣的double check的單例模式了。在多線程的情況下,既沒有同步(加鎖)帶來的性能下降,也沒有全局變量直接實例化帶來的資源浪費。
3.裝飾器
如果使用裝飾器,那么將會是這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import functools def singleton( cls ): ''' Use class as singleton. ''' 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 # # Sample use: # @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 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
def singleton( cls ): instance = cls () instance.__call__ = lambda : instance return instance # # Sample use # @singleton class Highlander: x = 100 # Of course you can have any attributes or methods you like. Highlander() is Highlander() is Highlander #=> True id (Highlander()) = = id (Highlander) #=> True Highlander().x = = Highlander.x = = 100 #=> True Highlander.x = 50 Highlander().x = = Highlander.x = = 50 #=> True |