使用Python来实现单例模式是一件很有意思的事情,在这个过程中我们会重新审视Python在创建类(Build Class)和构建实例时都调用了那些magic method。
同时我也想说的是Python的单例模块实现多种多样,但有些实现是存在问题的,在某些情况下是不可用或者影响系统的某些功能的。
1.利用Python的metaclass实现单例
先说我认为实现起来最地道的Python Singleton的实现方法。metaclass在Python中是一个很奇特的东西。Python在类的构建过程中会寻找metaclass,默认是type,如果我们在类中定义的metaclass,Python在类的构建中就会使用这个metaclass。
上面的描述主要体现在下面的代码逻辑中。
# ceval.c
static 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,直接返回。相同tpinit也会被调用两次,每次都会调用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.小结
上面三种方法其实都可以实现单例模式,就个人经验和喜好来说,第一种最好。