文章目录
Python单例模式详解
介绍
单例模式是一种常用的软件设计模式,目的是确保一个类只有一个实例,并提供一个访问该实例的全局访问点。在多线程环境中,单例模式能够避免对共享资源的多重占用,是构建资源管理器、工厂类等对象时的首选模式。
单例模式的用途
单例模式主要用于以下场景:
- 当类的构造函数被标记为私有时,保证无法在类外部实例化。
- 当系统中的某个类只需要一个全局实例时,例如配置管理器、线程池等。
- 资源共享,例如全局缓存、全局状态管理等。
实现单例模式
Python中实现单例模式有多种方法,以下分别展示几种典型的实现方式。
使用模块
由于Python的模块在第一次导入时会被初始化,之后再次导入时会直接使用已经加载的模块对象,所以可以通过简单地将所需要的类的实例化放在一个模块中来实现单例模式。
python
# singleton_module.py
class Singleton:
def __init__(self):
self.value = "Singleton Instance"
singleton_instance = Singleton()
使用时直接从模块中导入这个实例:
python
from singleton_module import singleton_instance
# 这里的singleton_instance已经是一个全局的单例
print(singleton_instance.value)
使用__new__
方法
在Python中,__new__
方法负责实例化对象。可以通过覆写这个方法来控制对象的创建过程,从而实现单例模式。
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self):
self.value = "Singleton Instance"
测试单例:
python
a = Singleton()
b = Singleton()
print(a == b) # 输出 True
print(a.value) # 输出 Singleton Instance
使用装饰器
可以创建一个装饰器来封装单例的创建逻辑,这使得单例模式的使用更加灵活和简单。
python
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 Singleton:
def __init__(self):
self.value = "Singleton Instance Decorated"
测试装饰器实现的单例:
python
a = Singleton()
b = Singleton()
print(a == b) # 输出 True
print(a.value) # 输出 Singleton Instance Decorated
使用元类
元类是创建类的类,可以通过自定义元类来控制类的创建过程。下面是使用元类实现单例模式的例子:
python
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def __init__(self):
self.value = "Singleton Instance with Meta"
测试元类实现的单例:
python
a = Singleton()
b = Singleton()
print(a == b) # 输出 True
print(a.value) # 输出 Singleton Instance with Meta
单例模式的优缺点
优点
- 确保一个类只有一个实例,并提供一个全局访问点。
- 可以延迟初始化,即在真正需要使用时才创建实例。
缺点
- 如果不需要单例,后期很难适应变化。
- 可能会导致代码难以测试。
- 在多线程环境中,需要特别注意实现方式,防止多线程同时创建多个实例。
总结
单例模式是一个非常有用的模式,在很多需要控制资源访问或者状态共享的场景下非常有用。Python提供了多种方式来灵活实现单例模式,可以根据具体需要选择合适的实现方式。
单例模式之线程安全
在多线程环境中,确保单例模式的线程安全是非常重要的。在Python等语言中,如果不适当地实现单例模式,确实有可能在多线程情况下创建多个实例,因此在某些实现方法中需要加锁来确保只创建一个实例。
线程安全的单例实现
以下是几种可以确保线程安全的单例实现方法:
使用线程锁
可以在创建实例的过程中使用线程锁来确保在任何时刻只有一个线程可以执行实例创建代码。
python
import threading
class Singleton:
_instance = None
_lock = threading.Lock() # 创建一个锁
def __new__(cls, *args, **kwargs):
with cls._lock: # 锁定代码块
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
这种方法利用了锁,保证了即使在多线程环境中也只能创建一个实例。
使用__new__
方法和双重检查锁定(Double-Checked Locking)
这是一个优化的线程安全单例实现,它在实例已经创建后避免了锁的开销。
python
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if not cls._instance:
with cls._lock:
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
这里使用了两层检查:外层检查避免每次都需要锁定,只有当实例尚未创建时才进行锁定和内层检查。
使用模块级别的单例
由于Python的模块在第一次导入时只会被加载一次,利用这一特性可以创建一个天然的线程安全的单例模式,如前文中模块实现的例子。这种方法在Python中是线程安全的,因为模块加载本身在Python中是线程安全的。
使用元类和线程锁
元类也可以结合线程锁来实现线程安全的单例模式。
python
class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
这种方法结合了元类的强大功能和线程锁的安全性,保证了即使在多线程环境中也只会创建一个实例。
总结
在多线程环境中实现单例模式时,确保线程安全是非常重要的。Python提供了多种方法来实现线程安全的单例,包括使用锁、双重检查锁定和利用模块加载的天然线程安全特性。选择合适的实现方式可以根据具体应用场景和性能要求来决定。
单例的懒汉模式与饿汉模式
在单例模式的实现中,"懒汉模式"和"饿汉模式"是两种常见的初始化策略,各有特点和适用场景。
饿汉模式
饿汉模式指的是在类加载时就立即初始化单例对象,无论之后会不会用到这个实例。这种方式的主要优点是实现简单,无需考虑多线程问题,实例在应用启动时就已经存在了。
饿汉模式的实现
在Python中,饿汉模式可以通过在模块级别直接实例化单例来实现:
python
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
# 在模块加载时就创建一个实例
singleton_instance = Singleton()
在这种方式中,singleton_instance
将在模块首次加载时创建,确保了单例对象的即时可用性。
懒汉模式
懒汉模式指的是类在第一次被使用时才创建实例,而不是在应用启动时。这种方式的优点是节约资源,实例只在需要时才会被创建。
懒汉模式的实现
懒汉模式通常需要考虑线程安全问题,特别是在多线程环境中需要确保只创建一个实例。以下是一个线程安全的懒汉式单例实现:
python
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls)
return cls._instance
这里使用了双重检查锁定来确保只有一个实例被创建,同时减少了锁的开销。
懒汉模式与饿汉模式的比较
- 初始化时机:饿汉模式在类加载时就完成实例的初始化,而懒汉模式在类的实例首次被请求时才初始化。
- 资源利用:懒汉模式在不需要实例时不会初始化,从而节约资源;饿汉模式可能会导致资源的早期占用。
- 性能考虑:饿汉模式无需考虑多线程同步问题,因此在性能上可能稍优;懒汉模式则需要通过同步机制保证线程安全,可能会稍微影响性能。
- 实现复杂度:懒汉模式在实现时需要考虑线程安全问题,代码相对复杂;饿汉模式实现简单。
选择策略
选择使用懒汉模式还是饿汉模式,主要依赖于具体的应用场景和需求:
- 如果确保初始化速度并且对启动性能不敏感,可以使用饿汉模式。
- 如果关注资源利用和延迟加载,懒汉模式可能是更好的选择。
总之,合理的选择单例的实现方式可以帮助更好地适应应用的需求,提高程序的效率和性能。
示例
配置文件单例模式(采用饿汉模式,无需考虑线程安全问题)
__init__.py
python
from .sensor_cfg import SensorConfig
from .sensor_cfg import Device, DeviceType, SensorConfigData
sensor_cfg.py
python
# sensor_cfg.py
import json
from dataclasses import dataclass
from typing import List, Dict, Optional, Set
CONFIG_PATH = 'config/sensor.json'
@dataclass
class Device:
address: int
type: int
location: str
@dataclass
class DeviceType:
typeId: int
description: str
@dataclass
class SensorConfigData:
modbusRtuDevices: List[Device]
deviceDefinition: List[DeviceType]
class SensorConfig:
"""
单例类用于管理传感器配置。
该类从一个JSON文件中读取并验证传感器配置,
确保在应用程序的生命周期中只加载一次。
"""
# 类属性
_instance = None # 单例模式保证类只有一个实例
def __new__(cls) -> 'SensorConfig':
# 如果实例不存在,创建新的实例
if cls._instance is None:
cls._instance = super(SensorConfig, cls).__new__(cls)
return cls._instance
def __init__(self) -> None:
if not hasattr(self, 'initialized'): # 保证初始化代码只执行一次
self.data: Optional[SensorConfigData] = None
self.config_path: str = CONFIG_PATH
self.load_config()
self.initialized: bool = True # 标记实例已被初始化
@classmethod
def get_instance(cls) -> 'SensorConfig':
if cls._instance is None:
cls._instance = cls()
return cls._instance
def load_config(self) -> None:
"""
从指定的config_path加载配置文件。
如果文件未找到或JSON无效则抛出错误。
"""
try:
with open(self.config_path, 'r', encoding='utf-8') as file:
# 加载JSON数据
self.config = json.load(file)
# Convert loaded JSON to dataclass instances
modbusRtuDevices = [Device(**device)
for device in self.config['modbusRtuDevices']]
deviceDefinition = [DeviceType(**type_info)
for type_info in self.config['deviceDefinition']]
self.data = SensorConfigData(
modbusRtuDevices=modbusRtuDevices,
deviceDefinition=deviceDefinition
)
# 校验加载的配置数据
self.validate_config()
except FileNotFoundError as exc:
raise FileNotFoundError(
f'配置文件 [{self.config_path}] 未找到,请检查路径。') from exc
except json.JSONDecodeError as exc:
raise ValueError("配置文件中的JSON格式无效。") from exc
def validate_config(self) -> None:
"""
验证加载的配置,确保所有设备类型都已定义,地址在有效范围内。
还会检查是否有重复的设备id和地址。
# sensor.json 示例
{
"modbusRtuDevices": [
{
"address": 1,
"type": 1,
"location": "仓库"
},
{
"address": 2,
"type": 2,
"location": "蒸馏室"
},
{
"address": 3,
"type": 3,
"location": "办公室1"
},
{
"address": 4,
"type": 3,
"location": "办公室2"
}
],
"deviceDefinition": [
{
"typeId": 1,
"description": "甲烷传感器"
},
{
"typeId": 2,
"description": "一氧化碳传感器"
},
{
"typeId": 3,
"description": "温湿度传感器"
}
]
}
"""
if self.data is None:
raise ValueError("配置数据未加载,无法验证配置。")
# 用于检查id和地址是否重复
device_type_ids: Set[int] = set()
used_addresses: Set[int] = set()
# 首先验证设备定义中的ID是否重复
for device_type in self.data.deviceDefinition:
type_id = device_type.typeId
if type_id in device_type_ids:
raise ValueError(f"在deviceDefinition中发现重复的typeId:{type_id}")
device_type_ids.add(type_id)
# 验证每个设备的配置
for device in self.data.modbusRtuDevices:
address = device.address
device_type = device.type
# 校验地址是否为有效的Modbus地址
if not (1 <= address <= 247):
raise ValueError(f"Modbus地址 {address} 无效。必须在1到247之间。")
# 检查地址是否重复
if address in used_addresses:
raise ValueError(f"在ModbusRTU设备中发现重复的地址:{address}")
used_addresses.add(address)
# 校验设备类型是否已在deviceDefinition中定义
if device_type not in device_type_ids:
raise ValueError(f"设备类型 {device_type} 在地址 {address} 上未定义。")
@property
def devices(self) -> List[Device]:
"""
返回设备配置信息。
"""
return self.data.modbusRtuDevices if self.data else []
@property
def device_types(self) -> Dict[int, str]:
"""
返回设备类型映射表。
"""
return {dt.typeId: dt.description for dt in self.data.deviceDefinition} if self.data else {}
# 饿汉模式:模块加载时就创建实例
SensorConfig()
# 使用示例
if __name__ == "__main__":
sensor_config = SensorConfig.get_instance() # 获取单例实例
print(sensor_config.devices) # 输出所有设备的详细信息
print(sensor_config.device_types) # 输出所有设备类型的映射表