1. 程式人生 > >Python設計模式中單例模式的實現及在Tornado中的應用

Python設計模式中單例模式的實現及在Tornado中的應用

實例 類變量 attribute rap all wrap 線程 ++ 出現

單例模式的實現方式
將類實例綁定到類變量上
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

@singleton
class 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 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?來看個這裏面簡單的單例模式,先來看看代碼:

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

這種情況看似還不錯,但是不能保證在多線程的環境下仍然好用,看圖:
201632180733229.png (683×463)

出現了多線程之後,這明顯就是行不通的。

1.上鎖使線程同步
上鎖後的代碼:

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++ 這些語言裏還可以利用全局變量的方式解決上面那種加鎖(同步)帶來的問題:

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 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

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

Python設計模式中單例模式的實現及在Tornado中的應用