Python 中单例模式的实现

2019-09-09

今天面试的时候,面试官问我了一个问题:Python 如何实现单例模式?说起来「单例模式」这个词我倒并不陌生,但从来没有实际的上手实现过,当时只在面试官的提示下写出了伪代码和思路,现在来这复盘,认真看看所有可行的实现方式。

单例模式是一个在软件设计中很常见的概念,即单例模式的类只能至多拥有一个实例。具体的应用场景可以考虑一个服务器中的 logger,且是一个在很多地方我们都会用到的同一个 logger,如果每次调用时我们都实例化一个新的,无疑是对服务器资源的浪费。这时候就可以用到单例模式,将 looger 变成一个无论多少次实例化,都只会拥有一个实例的类。

在 Python 中主要有这三个实现方式:

  • 改写 __new__
  • 使用装饰器 decorator
  • 使用元类 meteclass
  • 换个思路:模块

改写 __new__

这是比较符合直觉的一个方法之一,在类生成实例的时候,__init__ 之前首先被调用的是 __new__,所以我们可以在类中引入一个变量来存储第一次实例化过后的实例对象,之后的每一个实例都指向它。

class Logger(object):
    __instance = None    # 用于存储实例对象的类变量
    def __new__(cls, *args, **kwargs):
        if cls.__instance is None:
        # 调用父类的 __new__ 方法
            cls.__instance = super(Logger, cls).__new__(cls, *args, **kwargs)
        return cls.__instance
    
logger_1 = Logger()
logger_2 = Logger()
    
print(logger_1 is logger_2)    # True

使用装饰器 decorator

同样,使用装饰器也是一个比较显而易见的办法(然而面试时却没有想到,呵呵),在装饰器中使用一个私有的字典来存储被装饰类已经实例化的第一个对象。

from functools import wraps

def singleton(cls):
    __instances = {}
    @wraps(cls)
    def wrapper(*args, **kwargs):
        if cls not in __instances:
            __instances[cls] = cls(*args, **kwargs)
        return __instances[cls]
    return wrapper

@singleton
class Logger(object):
    pass

使用元类 meteclass

类的创建可以由更底层的元类来控制,所以使用元类也可以达到我们的目的,基本思路也跟之前相同。

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]
    
class Logger(metaclass=Singleton):
    pass

换个角度:模块

除了这些比较符合直觉的做法,还有没有什么更巧妙的办法?实际上模块在 Python 的解释运行中就有单例模式的影子。在第一次运行过后,模块的 .py 文件会被解释器生成一份 .pyc 文件,包含了编译过后生成的字节码,以期提高再次运行时的效率,也是如此,我们若是在一个模块中定义了我们的 logger,就可以在一次编译后直接获得一个单例类。

# logger.py
class Logger(object):
    pass

my_logger = Logger()

# mian.py
from logger import my_logger
Tagged in : Python Singleton