- Published on
Python 如何实现单例模式
- Authors
- Name
- 阮达达
- Github
- @ruandada
简介
面试时,面试官常常会问一个经典问题:如何确保某个类在整个程序中只有一个实例?这就是经典的单例模式(Singleton Pattern)。
单例模式听起来很简单:一个类只能有一个实例。但在 Python 中,实现单例模式的方式还真不少。我整理了几种常用的实现方法,一起来看看。
__new__
方法
1. 最直观的方式:重写 先来看最容易理解的实现方式。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("线程安全的数据库连接...")
实际项目中怎么选?
说了这么多种实现方式,实际项目中该用哪种呢?我的建议是:
- 如果是简单的配置类,直接用模块级别的实现就好
- 如果要把现有的类改造成单例,用装饰器方式最方便
- 如果是新项目,而且团队成员 Python 水平都不错,可以考虑用元类方式
- 如果项目很简单,用
__new__
方法也没问题
记住,代码是写给人看的,选择一种团队成员都能看懂的实现方式比什么都重要。