原文地址:
使用Python来实现单例模式是一件很有意思的事情,在这个过程中我们会重新审视Python在创建类(Build Class)和构建实例时都调用了那些magic method。
同时我也想说的是Python的单例模块实现多种多样,但有些实现是存在问题的,在某些情况下是不可用或者影响系统的某些功能的。
1.利用Python的metaclass实现单例
先说我认为实现起来最地道的Python Singleton的实现方法。metaclass在Python中是一个很奇特的东西。Python在类的构建过程中会寻找metaclass,默认是type,如果我们在类中定义的metaclass,Python在类的构建中就会使用这个metaclass。
上面的描述主要体现在下面的代码逻辑中。# ceval.cstatic PyObject * build_class(PyObject *methods, PyObject *bases, PyObject *name) { PyObject *metaclass = NULL, *result, *base; // 0.先尝试从用户自定义的类中查询__metaclass__ if (PyDict_Check(methods)) metaclass = PyDict_GetItemString(methods, "__metaclass__"); if (metaclass != NULL) Py_INCREF(metaclass); else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { // 1.从基类获取__class__作为metaclass base = PyTuple_GET_ITEM(bases, 0); metaclass = PyObject_GetAttrString(base, "__class__"); if (metaclass == NULL) { PyErr_Clear(); // 2.使用ob_type metaclass = (PyObject *)base->ob_type; Py_INCREF(metaclass); } } else { // 3.如果这个类连基类都没有,使用全局global的__metaclass__ PyObject *g = PyEval_GetGlobals(); if (g != NULL && PyDict_Check(g)) metaclass = PyDict_GetItemString(g, "__metaclass__"); if (metaclass == NULL) // 4.使用PyClass_Type metaclass = (PyObject *) &PyClass_Type; Py_INCREF(metaclass); } ......
metaclass创建好之后,我们要使用metaclass创建对应的类,并初始化创建处理的类。
类的创建主要在type_call是实现,我们在大体提到了type_call里面的部分逻辑,主要涉及tp_new和tp_init。之后,在初始化类中会调用PyObject_Call方法。PyObject_Call方法会调用metaclass的tp_call方法,并且返回此类对应的实例,因此为了实现单例模式,我们可以在tp_call这个方法上做手脚。class Singleton(type): _instance = {} def __call__(cls, *args, **kwargs): if cls not in Singleton._instance: Singleton._instance[cls] = type.__call__(cls, *args, **kwargs) return Singleton._instance[cls] class A(object): __metaclass__ = Singleton a1 = A() a2 = A() print id(a1) == id(a2)
上面实例a1和实例a2的id是相同。例外__call__即使上面所只得tp_call。
2.利用__new__实现单例模式
另一种方法是我们在tp_new的时候去检查此类有没有被创建,创建了就直接返回对应的实例。
class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): if Singleton._instance is None: Singleton._instance = object.__new__(cls, *args, **kwargs) return Singleton._instance class A(Singleton): def __init__(self): print "init A" a1 = A() a2 = A() print id(a1) == id(a2)
上面这种方法也可以实现单例,同时也存在着风险。
- 如果子类重载了__new__方法,那么父类的__new__方法就不起作用了
- 如果运行上面的代码会发现打印出两次"init A", 因为在这种情况下tp_new会被调用两次,第二次由于_instance不等于None,直接返回。相同tp_init也会被调用两次,每次都会调用A的__init__方法。如果不小心在__init__中做了一些初始化的逻辑,那么每次构建一个实例都要进行初始化了(是不是感觉和单例模式的初衷相违背了呢)。
3.利用修饰器实现单例模式
_instance = {} def Singleton(cls): def wrapper(*args, **kwargs): if cls not in _instance: _instance[cls] = cls(*args, **kwargs) return _instance[cls] return wrapper @Singleton class A(object): def __init__(self): print "init" a = A() b = A() print id(a) print id(b) print type(A)
运行上面的代码,一切看起来都合情合理,"init"也只打印了一次。但看最后一行的输出,A的类型为function。好的,显然没有错,我们用修饰器修饰了。但A的类型变化了会产生什么负面作用是很难把握的。随着工程的扩大,A是什么类型开发者可能已经不了解了,因此也会导致很多问题出来,并且很难去定位问题。
4.小结
上面三种方法其实都可以实现单例模式,就个人经验和喜好来说,第一种最好。