python实现单例_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_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.小结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值