Python面试题系列之15: 如何实现一个单例?
Question
如何实现一个单例?
知识点详解
单例(Singleton),一种设计模式,应用该模式的类只会生成一个实例。它保证了在程序的不同位置都可以,且仅能取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。
我们回顾下在 Python 中的类、实例,是通过哪些方法以怎样的顺序被构造的。
简单来说,元类(metaclass)
可以通过__metaclass__
方法创造出类(class)
,而类(class)
通过__new__
方法创造出实例(instance)
。
在创造类的过程中或者创造实例的过程中稍加控制,从而可以达到最后产生的实例都是一个对象的目的,即实现了单例。
方法一:利用 new 方法
为了使类只能出现一个实例,我们可以使用__new__
来控制实例的创建过程,达到实现单例模式的目的。
class Singleton(object):
_instance = None
def __new__(cls, *args, **kw):
if cls._instance is None:
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
def __init__(self):
pass
a = Singleton()
b = Singleton()
print(id(a) == id(b)) # True
这里将一个类的实例绑定到类变量_instance上,如果_instance为None,说明该类还没有实例化过,实例化该类并返回;否则直接返回 _instance 存放的实例。
方法二:利用元类(metaclass)
我们在类的创建时进行干预,从而达到实现单例的目的。
# 拦截(intercepting) class的创建
class Singleton(type):
_instance = None
# 通过重写__call__拦截实例的创建
def __call__(cls, *args, **kw):
if not cls._instance:
cls._instance = super().__call__(*args, **kw)
return cls._instance
class MyClass(metaclass=Singleton):
pass
a = MyClass()
b = MyClass()
print(id(a) == id(b)) # True
这里我们将metaclass指向 Singleton 类,让Singleton中的type来创造新的 MyClass实例。
元类可以控制类的创建过程,它主要做了三件事:拦截类的创建;修改类的定义;返回修改后的类。
方法三:利用(函数)装饰器
装饰器可以动态的修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例:
def Singleton(cls):
_instances = {}
def get_instance(*args, **kwargs):
if cls not in _instances:
_instances[cls] = cls(*args, **kwargs)
return _instances[cls]
return get_instance
@Singleton
class MyClass:
def __init__(self):
pass
a = MyClass()
b = MyClass()
print(id(a) == id(b)) # True
这里我们定义了一个装饰器singleton
,它返回了一个内部函数get_instance
,该函数会判断某个类是否在字典_instances
中,如果不存在,则会将cls
作为key
、cls(*args, **kwargs)
作为value
存到instances
中,否则,直接返回_instances[cls]
。
方法四:利用(类)装饰器
上面我们使用函数装饰器实现了单例,用类装饰器来实现的思路与其类似,直接看具体代码:
class Singleton(object):
def __init__(self, cls):
self._cls = cls
self._instance = {}
def __call__(self):
if self._cls not in self._instance:
self._instance[self._cls] = self._cls()
return self._instance[self._cls]
@Singleton
class MyClass:
def __init__(self):
pass
a = MyClass()
b = MyClass()
print(id(a) == id(b)) # True
方法五:利用StaticMethod
class Singleton(object):
_instance = None
def __init__(self):
raise SyntaxError('can not instance, please use get_instance')
@staticmethod
def get_instance():
if Singleton._instance is None:
Singleton._instance = object.__new__(Singleton)
return Singleton._instance
a = Singleton.get_instance()
b = Singleton.get_instance()
print(id(a) == id(b)) # True
该方法的要点是在__init__
抛出异常,禁止通过类来实例化,只能使用静态get_instance
函数来获取实例,通过父类object.__new__
来实例化。
方法六:利用ClassMethod
和方法五的实现思路类似,只是这里采用了classmethod,代码:
class Singleton(object):
_instance = None
def __init__(self):
raise SyntaxError('can not instance, please use get_instance')
@classmethod
def get_instance(cls):
if Singleton._instance is None:
Singleton._instance = object.__new__(Singleton)
return Singleton._instance
a = Singleton.get_instance()
b = Singleton.get_instance()
print(id(a) == id(b)) # True
方法七:利用名字覆盖
这里采用的是一种名字覆盖的思路
class Singleton(object):
def foo(self):
print('foo')
def __call__(self):
return self
Singleton = Singleton()
Singleton.foo()
a = Singleton()
b = Singleton()
print(id(a) == id(b)) # True
方法八:使用模块
Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码。
因此我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
# MySingleton.py
class MySingleton:
def foo(self):
pass
sinleton = MySingleton()
将上面的代码保存在文件MySingleton.py中,然后这样使用:
from MySingleton import sinleton
singleton.foo()
方法九:共享属性
所谓单例就是所有引用(实例、对象)拥有相同的状态(属性)和行为(方法),同一个类的所有实例天然拥有相同的行为(方法),只需要保证同一个类的所有实例具有相同的状态(属性)即可。
class Singleton(object):
_state = {}
def __new__(cls, *args, **kw):
ob = super(Singleton, cls).__new__(cls, *args, **kw)
ob.__dict__ = cls._state
return ob
class MyClass(Singleton):
x = 666
a = MyClass()
b = MyClass()
print(id(a) == id(b))
>>> False
我们可以看到a和b是两个不同的对象,我们再看看它们的__dict__
属性呢?
print(id(a.__dict__))
>>> 2679828344048
print(id(b.__dict__))
>>> 2679828344048
print(id(a.x) == id(b.x))
>>> True
不难发现这两不同的对象a、b,却共享了相同属性。所有实例共享属性的最简单直接的方法就是__dict__
属性指向(引用) 同一个字典(dict)。
Answer
实现单例模式的方法,详见上文内容。
后记
本文介绍了多种方法来实现单例模式,这其中涉及到了很多的Python高级语法,诸如decorator、metaclass、new、type、super等等。在面试过程以及实际工程项目中,根据实际情况来按需实现即可。关于单例模式的实现,你还能想到有哪些方式呢?
欢迎大家在评论区留言说出自己的看法。好了,以上就是本篇全部内容。
备注:本篇首发于知识星球「人人都是Pythonista」。