單例模式的實現方式
將類執行個體綁定到類變數上
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__以失去單例特性
class D(Singleton): def __new__(cls, *args): return super(D, cls).__new__(cls, *args)
使用裝飾器實現
def singleton(_cls): inst = {} def getinstance(*args, **kwargs): if _cls not in inst: inst[_cls] = _cls(*args, **kwargs) return inst[_cls] return getinstance@singletonclass MyClass(object): pass
問題是這樣裝飾以後返回的不是類而是函數,當然你可以singleton裡定義一個類來解決問題,但這樣就顯得很麻煩了
使用__metaclass__,這個方式最推薦
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中的單例模式:
class IOLoop(object): @staticmethod def instance(): """Returns a global `IOLoop` instance.Most applications have a single, global `IOLoop` running on themain thread. Use this method to get this instance fromanother 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?來看個這裡面簡單的單例模式,先來看看代碼:
class Singleton(object): @staticmathod def instance(): if not hasattr(Singleton, '_instance'): Singleton._instance = Singleton() return Singleton._instance
在 Python 裡,可以在真正的建構函式__new__裡做文章:
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.上鎖使線程同步
上鎖後的代碼:
import threadingclass 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++ 這些語言裡還可以利用全域變數的方式解決上面那種加鎖(同步)帶來的問題:
class Singleton { private static Singleton instance = new Singleton(); private Singleton() {} public static Singleton getInstance() { return instance; } }
在 Python 裡就是這樣了:
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.裝飾器
如果使用裝飾器,那麼將會是這樣:
import functoolsdef 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:#@singletonclass Foo: def __new__(cls): cls.x = 10 return object.__new__(cls) def __init__(self): assert self.x == 10 self.x = 15assert Foo().x == 15Foo().x = 20assert Foo().x == 20
def singleton(cls): instance = cls() instance.__call__ = lambda: instance return instance## Sample use#@singletonclass Highlander: x = 100 # Of course you can have any attributes or methods you like.Highlander() is Highlander() is Highlander #=> Trueid(Highlander()) == id(Highlander) #=> TrueHighlander().x == Highlander.x == 100 #=> TrueHighlander.x = 50Highlander().x == Highlander.x == 50 #=> True