0%

Python3 单例模式:确保类仅有一个实例

Python3 单例模式:确保类仅有一个实例

单例模式(Singleton Pattern)是一种常用的设计模式,它确保一个类在整个程序生命周期中只创建一个实例,并提供一个全局访问点。这种模式适合用于配置管理、日志记录、数据库连接等场景,避免资源浪费或状态不一致。

本文将介绍 Python 中实现单例模式的多种方法,及其适用场景和优缺点。

单例模式的核心思想

单例模式的核心目标是:

  1. 类只能有一个实例
  2. 类必须自行创建这个实例
  3. 类必须向整个程序提供这个实例的全局访问点

简单来说,无论尝试创建多少次类的实例,都只会得到同一个对象。

1
2
3
4
5
6
7
8
9
# 单例模式预期效果
class Singleton:
# 实现单例的代码...

# 多次实例化,得到同一个对象
s1 = Singleton()
s2 = Singleton()

print(s1 is s2) # True(证明是同一个实例)

Python 实现单例模式的常用方法

方法 1:使用 __new__ 方法(最常用)

__new__ 是 Python 中用于创建实例的特殊方法(在 __init__ 之前调用)。通过重写 __new__,可以控制实例的创建过程。

使用__new__方法实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Singleton:
# 类属性:存储唯一实例
_instance = None

def __new__(cls, *args, **kwargs):
# 如果实例不存在,则创建;否则直接返回已有实例
if not cls._instance:
# 调用父类的__new__创建实例
cls._instance = super().__new__(cls)
return cls._instance

def __init__(self, value):
# 注意:__init__仍会被多次调用,需处理初始化逻辑
if not hasattr(self, 'initialized'): # 避免重复初始化
self.value = value
self.initialized = True

# 测试
s1 = Singleton("first")
s2 = Singleton("second")

print(s1 is s2) # True(同一个实例)
print(s1.value) # first(初始化只执行一次)
print(s2.value) # first(第二次初始化未生效)

关键点

  • _instance 类属性存储唯一实例
  • __new__ 方法控制创建逻辑:只在首次调用时创建实例
  • 需要处理 __init__ 被多次调用的问题(可通过标志位避免重复初始化)

方法 2:使用装饰器(更灵活)

装饰器可以动态修改类的行为,通过装饰器包装类,实现单例效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def singleton(cls):
"""单例装饰器"""
instances = {} # 存储类与实例的映射

def wrapper(*args, **kwargs):
# 如果类未实例化,则创建实例并缓存
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper

# 应用装饰器
@singleton
class DatabaseConnection:
def __init__(self, db_url):
self.db_url = db_url
print(f"连接到数据库:{self.db_url}")

# 测试
db1 = DatabaseConnection("mysql://localhost:3306/db1")
db2 = DatabaseConnection("mysql://localhost:3306/db2") # 实际不会创建新连接

print(db1 is db2) # True
print(db1.db_url) # mysql://localhost:3306/db1(首次初始化生效)

优点

  • 代码复用性好,可装饰多个类
  • 实现与业务逻辑分离,符合单一职责原则
  • 无需修改类的内部实现

方法 3:使用元类(最彻底的方式)

元类(Metaclass)是创建类的 “类”,控制类的创建过程。通过自定义元类,可以从根本上确保类只创建一个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class SingletonMeta(type):
"""单例元类"""
_instances = {} # 存储类与实例的映射

def __call__(cls, *args, **kwargs):
# 元类的__call__方法控制类的实例化
if cls not in cls._instances:
# 创建实例并缓存
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]

# 使用元类(Python3 中通过 metaclass 参数指定)
class Config(metaclass=SingletonMeta):
def __init__(self):
self.settings = {}
print("初始化配置")

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

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

# 测试
config1 = Config()
config1.set("theme", "dark")

config2 = Config()
print(config1 is config2) # True
print(config2.get("theme")) # dark(共享同一份配置)

优点

  • 最彻底的实现方式,从类的创建层面保证单例
  • 支持继承场景(比 __new__ 方法更可靠)
  • 实例化过程完全受控制

方法 4:模块级单例(最简单方式)

Python 模块在首次导入时执行,后续导入会直接使用已加载的模块对象。利用这一特性,可以轻松实现单例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 文件名:logger.py(作为单例模块)

class Logger:
def __init__(self):
self.logs = []

def log(self, message):
self.logs.append(message)
print(f"日志:{message}")

def get_all_logs(self):
return self.logs

# 模块级实例(首次导入时创建,后续导入直接使用)
logger = Logger()


# 使用方式(在其他文件中)
# from logger import logger
#
# logger.log("程序启动")
# logger.log("处理数据")
# print(logger.get_all_logs()) # ['程序启动', '处理数据']

优点

  • 实现最简单,利用 Python 内置特性
  • 无需额外代码,天然支持单例
  • 适合简单场景(如日志工具、配置管理)

单例模式的线程安全问题

在多线程环境中,上述基础实现可能存在线程安全问题(多个线程同时判断 _instanceNone,导致创建多个实例)。

解决方法:添加线程锁(threading.Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import threading

class ThreadSafeSingleton:
_instance = None
_lock = threading.Lock() # 线程锁

def __new__(cls, *args, **kwargs):
# 双重检查锁定(提高性能)
if not cls._instance:
with cls._lock: # 加锁确保只有一个线程进入创建逻辑
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance

# 测试多线程场景
def create_instance():
s = ThreadSafeSingleton()
print(f"实例ID:{id(s)}") # 所有线程创建的实例ID相同

# 创建多个线程
threads = [threading.Thread(target=create_instance) for _ in range(5)]

# 启动所有线程
for t in threads:
t.start()

# 等待所有线程完成
for t in threads:
t.join()

关键点

  • 使用 threading.Lock 确保同一时间只有一个线程执行实例创建逻辑
  • 双重检查锁定(Double-Checked Locking):先判断实例是否存在,再加锁,提高性能

单例模式的适用场景

  1. 资源共享类
    • 数据库连接池
    • 日志管理器
    • 配置管理器
  2. 控制访问唯一资源
    • 系统中的打印机对象
    • 网络连接对象
  3. 状态一致性要求高的场景
    • 全局计数器
    • 会话管理器

单例模式的优缺点

优点:

  • 减少内存开销(只创建一个实例)
  • 避免资源多重占用(如数据库连接)
  • 提供全局访问点,方便状态管理

缺点:

  • 违反单一职责原则(既负责业务逻辑,又负责实例管理)
  • 可能隐藏依赖关系(全局访问导致代码耦合)
  • 不利于单元测试(难以模拟和隔离)
  • 多线程环境下需要额外处理线程安全

总结

Python 中实现单例模式的方法各有优劣,选择依据场景而定:

  • 简单场景:优先使用模块级单例(最简单)
  • 需要装饰多个类:使用装饰器实现
  • 复杂继承场景:使用元类实现
  • 多线程环境:必须添加线程锁确保安全

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10