Python中的单例模式实现

使用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_newtp_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)

上面这种方法也可以实现单例,同时也存在着风险。

  1. 如果子类重载了__new__方法,那么父类的__new__方法就不起作用了
  2. 如果运行上面的代码会发现打印出两次”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.小结

上面三种方法其实都可以实现单例模式,就个人经验和喜好来说,第一种最好。