万物之中, 希望至美.

Python单例模式

2018.10.11

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
comments powered by Disqus