Python单例模式
Design ptterns are discoverd, not invented.- Alex Martelli
1.使用模块
因为在 Python 中导入模块时,第一次导入会生成.pyc
文件,第二次导入时就直接加载.pyc
文件了,而不会再次执行模块中的代码,所以 Python 中使用模块是最天然的单例模式。
# module.py
class Singleton:
pass
instance = Singleton()
使用方式:
import module
# or from module import instance
module.instance
2.__new__
Python 中真正的构造方法是__new__
,而不是__init__
,这时我们就可以在__new__
中做文章:
class Singleton:
_instance = {}
def __new__(cls, *args, **kwargs):
if cls not in cls._instance:
cls._instance[cls] = super(Singleton, cls).__new__(cls)
return cls._instance[cls]
这种方式在并发的情况下可能会发生意外,为了解决这个问题,引入一个带锁的版本:
import threading
class Singleton:
_instance = {}
locker = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls in cls._instance:
return cls._instance[cls]
cls.locker.acquire()
try:
if cls in cls._instance:
return cls._instance[cls]
cls._instance[cls] = super(Singleton, cls).__new__(cls)
finally:
cls.locker.release()
return cls._instance[cls]
利用经典的双检查锁机制,确保了在并发环境下 Singleton 的正确实现。但这个方案并不完美,至少还有以下两个问题:
- 如果 Singleton 的子类重写了
__new__()
方法,会覆盖或者干扰 Singleton 类中__new__()
的执行,虽然这种情况出现的概率极小,但也不可忽视。 - 如果子类有
__init__()
方法,那么每次示例化该 Singleton 的时候,__init__()
都会被调用到,这个显然是不应该的,__init__()
只应该在创建实例的时候被调用一次。
这两个问题当然可以解决,比如第一个问题可以通过文档告知该类的使用者,如果要重载 Singleton 的__new__()
方法,请务必记得调用父类的__new__()
方法;而第二个问题也可以通过替换掉__init__()
方法来确保它只调用一次。
但是,为了实现一个单例,做大量的水面之下的工作让人感觉相当不 Pythonic,这也引起了 Python 社区的反思,有人开始重新审视 Python 的语法元素,发现模块其实是天然的单例实现方式(即第一种实现方式),这是因为:
- 所有的变量都会绑定到模块
- 模块只初始化一次
- import 机制是线程安全的(保证了在并发状态下模块也只有一个实例)
3.共享属性(Borg模式)
Borg 模式与单例模式其实是不同的,单例模式关注的是对象一致,然而这可能未必是我们使用单例模式的原因,有可能我们关注的是实例的状态一致,这种情况下就可以使用 Borg 模式。
class Borg:
_state = {}
def __init__(self):
self.__dict__ = self._state
4.使用元类
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
# python2
class YourClass(object):
__metaclass__ = Singleton
# python3
class YourClass(metaclass=Singleton):
pass