前言
单例模式是设计模式(Design Pattern)中最简单、最容易理解的一种,维基百科[1]的定义如下:
单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
单例模式的主要优点是共享资源和减少资源消耗,主要应用于IO或数据库的线程池,缓存,日志,对话和需共享数据的资源等,但是在实现情况中滥用单例模式会带来很多意想不到的问题,本文重点在于介绍几种Python实现单例模式的方法,这里就不再展开论述了。文中所演示的代码都会托管在Github上。
简单实现
首先,我们先尝试用Python内部类(嵌套类)来实现单例模式:
#coding=utf-8
class Singleton:
"""单列类
"""
class __MyClass:
"""实际生成实例的类
"""
def __init__(self, arg):
"""初始化并赋值"""
self.foo = arg
def display(self):
"""返回实例的id和属性值"""
return (id(self), self.foo)
# 类属性
_instance = None
def __init__(self, arg):
if not Singleton._instance:
Singleton._instance = Singleton.__MyClass(arg)
else:
Singleton._instance.foo = arg
def __getattr__(self, attr):
return getattr(self._instance, attr)
注意实际生成实例的类是内部的“__MyClass”类,前面的双下划线代表这是一个私有的类,用户不能再外面直接访问它。而在"__MyClass"类外封装了一个“Singleton”类,这个类的任务就是在初始化时保证整个上下文中只有一个实例,实现的方式很简单。用一个私有属性_instance保存当前生成的实例,在初始化时判断实例是否为None,如果是就用“__MyClass”类生成一个新实例并赋值给_instance,否就直接返回或调用当前_instance的实例。最后用"__MyClass"里的实现的方法测试一下:
if __name__ == "__main__":
"""测试"""
s1 = Singleton("bar")
s2 = Singleton("zoo")
print(s1.display())
print(s2.display())
# output
>(41706760L, 'zoo')
>(41706760L, 'zoo')
基类
现在我们考虑将inner class拆分出来,因为在Python类实例化时会调用__new__方法[2]来生成实例,所以我们可以先继承“Singleton”类,然后通过重写基类的__new__方法让其实现单例模式:
#coding=utf-8
class Singleton(object):
"""单例类
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(Singleton):
"""实际生成实例的类
"""
def __init__(self, arg):
self.foo = arg
def display(self):
return (id(self), self.foo)
测试结果:
if __name__ == "__main__":
s1 = MyClass("bar")
s2 = MyClass("zoo")
print(s1.display())
print(s2.display())
assert s1 is s2
# output
>(40882416L, 'zoo')
>(40882416L, 'zoo')
装饰器
第三种就是最常见的用装饰器来实现单列模式:
#coding=utf-8
def singleton(cls):
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class MyClass:
"""实际生成实例的类
"""
foo = "foo"
def display(self):
return (id(self))
@singleton
class OtherClass:
"""另一个类
"""
pass
装饰器的实现过程是将生成的实例都放到一个名为instances的Dict中映射好,这样每次在类初始化时先检查instances中是否已经包含有例化好的实例,有就直接返回是咧,没有则调用类初始化一个并赋值给instances列表。装饰器的好处在于用一个Dict列表来管理所有需要实现单例模式的类,更简便和通用化。代码的测试结果如下:
if __name__ == "__main__":
s1 = MyClass()
s1.foo = "bar"
print(s1.display(), s1.foo)
s2 = MyClass()
s2.foo = "zoo"
print(s2.display(), s2.foo)
assert s1 is s2
s3 = OtherClass()
s4 = OtherClass()
assert s3 is s4
元类
如果希望不仅仅是通过限制而是在源头上就创建一个单例类,我们需要用到元类来实现,元类可以参考Stackoverflow[3]上的一个解答。简单的说就是Python中的类也是一种对象,被称为类对象。类对象可以通过元类type来创建,而在此过程中会调用type的 __call__ 方法。所以我们只要在type创建类对象的过程中重写 __call__ 方法,在其中加入相应的创建单例的逻辑即可实现单例模式,具体代码实现如下:
#coding=utf-8
class Singleton(type):
def __call__(cls, *args, **kwargs):
"""重写,实现单例模式"""
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
class MyClass(object):
# 指定元类
__metaclass__ = Singleton
def display(self):
return (id(self))
代码的测试与前面类似,这里就不再累述了。
线程安全
最后,需要注意的是单例模式在多线程下可能会出现线程安全的问题,这时候就需要在单例的初始化过程中加上线程同步锁来避免,但这样又会降低整体的性能,具体可以参考这篇文档。
参考
[1]维基百科
[2]Python官方文档
[3]Stackoverflow