Published on

Python 如何实现单例模式

Authors

简介

面试时,面试官常常会问一个经典问题:如何确保某个类在整个程序中只有一个实例?这就是经典的单例模式(Singleton Pattern)。

单例模式听起来很简单:一个类只能有一个实例。但在 Python 中,实现单例模式的方式还真不少。我整理了几种常用的实现方法,一起来看看。

1. 最直观的方式:重写 __new__ 方法

先来看最容易理解的实现方式。Python 中创建对象时,是先调用 __new__ 再调用 __init__ 的。所以我们可以在 __new__ 中做点手脚:

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

    def __init__(self):
        # 初始化代码(如果需要)
        pass

# 测试一下
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # 输出: True

这种方式的好处是直观易懂,看代码就能明白是怎么回事。缺点也很明显:每次写单例类都得复制这段代码,很啰嗦。

2. 优雅的装饰器方式

有没有更优雅的方式?有,用装饰器:

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 Database:
    def __init__(self):
        # 假设这里是数据库连接代码
        print("连接数据库...")

# 测试一下
db1 = Database()
db2 = Database()
print(db1 is db2)  # 输出: True

这下好了,想要把任何类变成单例,只需要加个 @singleton 装饰器就行。不过要注意,使用装饰器可能会让类的继承关系变得有点复杂。

3. 炫技派:使用元类

如果你想在 Python 面试中炫技,可以用元类来实现单例模式:

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Config(metaclass=Singleton):
    def __init__(self):
        # 配置初始化代码
        print("加载配置...")

# 测试一下
config1 = Config()
config2 = Config()
print(config1 is config2)  # 输出: True

这种方式最优雅,也最 Pythonic。但说实话,除非你对 Python 的类机制特别熟悉,否则还是别用这种方式,容易把自己绕晕。

4. 懒人方式:直接用模块

其实,Python 中最简单的单例模式实现,就是直接用模块。因为 Python 的模块在导入时天然就是单例的:

# config.py
class Config:
    def __init__(self):
        self.settings = {}

    def set(self, key, value):
        self.settings[key] = value

    def get(self, key):
        return self.settings.get(key)

# 创建全局实例
config = Config()

# 在其他文件中使用
from config import config  # 所有地方导入的都是同一个实例

这种方式最符合 Python 的哲学:简单就是美。但缺点是灵活性差了点,不是所有场景都适用。

多线程环境下的坑

如果你的程序是多线程的,上面的实现方式可能会出问题。这里有个线程安全的装饰器实现:

from threading import Lock

def thread_safe_singleton(cls):
    _instances = {}
    _lock = Lock()

    def get_instance(*args, **kwargs):
        if cls not in _instances:
            with _lock:
                # 双重检查锁定
                if cls not in _instances:
                    _instances[cls] = cls(*args, **kwargs)
        return _instances[cls]

    return get_instance

@thread_safe_singleton
class Database:
    def __init__(self):
        print("线程安全的数据库连接...")

实际项目中怎么选?

说了这么多种实现方式,实际项目中该用哪种呢?我的建议是:

  1. 如果是简单的配置类,直接用模块级别的实现就好
  2. 如果要把现有的类改造成单例,用装饰器方式最方便
  3. 如果是新项目,而且团队成员 Python 水平都不错,可以考虑用元类方式
  4. 如果项目很简单,用 __new__ 方法也没问题

记住,代码是写给人看的,选择一种团队成员都能看懂的实现方式比什么都重要。