在Python中,单例模式有多种实现方式,下面介绍几种常见的形式:
-
使用模块:Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。
-
使用装饰器:使用装饰器来装饰一个类,使其只能生成一个实例。
-
使用类方法(new):通过重写类的__new__方法来实现单例。
-
使用元类(metaclass):通过元类来控制类的创建过程,确保只生成一个实例。
-
使用共享属性:通过将实例的__dict__属性指向同一个字典,从而使得多个实例共享相同的状态(但这并不是严格意义上的单例,因为多个实例的id可能不同,但状态共享)。
下面分别给出这些方式的代码示例:
1. 使用模块
创建一个模块,比如叫做singleton_module.py,里面定义需要的变量和函数。然后其他地方导入这个模块,这个模块就是单例的。
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 MyClass:
pass
3. 使用__new__方法
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
# 或者使用更简洁的方式,利用基类
class SingletonBase:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls, *args, **kwargs)
return cls._instance
class MyClass(SingletonBase):
pass
4. 使用元类
class SingletonMeta(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 MyClass(metaclass=SingletonMeta):
pass
5. 使用共享属性(Borg模式)
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class MyClass(Borg):
pass
6. 使用全局变量(不推荐,但也是一种方式)
在一个模块中定义一个全局变量,然后通过函数来获取这个变量,确保只有一个实例。
7. 使用线程安全的单例模式(适用于多线程环境)
import threading
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().__new__(cls, *args, **kwargs)
return cls._instance
# 或者使用模块级的锁
注意:在多线程环境下,需要确保单例的创建过程是线程安全的。上面的例子中,我们使用了双重检查锁定模式(Double-Checked Locking Pattern)。
8. 使用lru_cache(Python 3.2+)
from functools import lru_cache
@lru_cache(maxsize=1)
class MyClass:
pass
但请注意,lru_cache通常用于函数,用于类时,它缓存的是类的实例,但类本身可能被多次调用,但返回的是同一个实例。然而,这种方式并不常见,而且需要注意类的参数问题。
实际上,单例模式在Python中并不像在静态语言中那样必要,因为我们可以通过模块来共享状态。但是,如果需要,以上几种方式都可以实现单例。
选择哪种方式取决于具体需求,比如是否要求线程安全,是否希望延迟初始化等。通常情况下,使用模块或者装饰器的方式比较简单,而使用元类的方式则更加灵活和强大。
以下是 Python 中单例模式的几种常见实现方式:
1. 使用模块实现(推荐)
Python 模块本身就是天然的单例模式
# singleton_module.py
class Singleton:
def __init__(self):
self.value = None
def do_something(self):
return f"Doing something with {self.value}"
singleton_instance = Singleton()
# 在其他文件中使用
from singleton_module import singleton_instance
singleton_instance.value = "Hello"
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("Database initialized")
self.connection = None
def connect(self):
self.connection = "Connected"
# 使用
db1 = Database() # 输出: Database initialized
db2 = Database()
print(db1 is db2) # True
3. 使用 __new__ 方法实现
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name=None):
if not hasattr(self, 'initialized') or name:
self.name = name
self.initialized = True
# 使用
s1 = Singleton("First")
s2 = Singleton("Second")
print(s1 is s2) # True
print(s1.name) # "First" (不会变成"Second")
4. 使用元类实现
class SingletonMeta(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 Logger(metaclass=SingletonMeta):
def __init__(self, log_file="app.log"):
self.log_file = log_file
self.logs = []
def log(self, message):
self.logs.append(message)
print(f"[LOG] {message}")
# 使用
logger1 = Logger("app.log")
logger2 = Logger("another.log") # 参数被忽略
print(logger1 is logger2) # True
print(logger1.log_file) # "app.log"
5. Borg 模式(共享状态单例)
所有实例共享相同状态,但不是严格意义上的单例
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
self.state = "Init"
def __str__(self):
return self.state
class YourClass(Borg):
pass
# 使用
obj1 = YourClass()
obj2 = YourClass()
obj1.state = "Running"
print(obj2.state) # "Running"
print(obj1 is obj2) # False (不同对象但状态共享)
6. 线程安全的单例
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)
return cls._instance
def __init__(self, config=None):
if not hasattr(self, 'initialized'):
self.config = config or {}
self.initialized = True
7. 使用 functools.lru_cache (Python 3.8+)
from functools import lru_cache
class SingletonClass:
def __init__(self, value):
self.value = value
@lru_cache(maxsize=1)
def get_singleton(value):
return SingletonClass(value)
# 使用
obj1 = get_singleton("test")
obj2 = get_singleton("test") # 返回缓存的实例
print(obj1 is obj2) # True
8. 使用类方法实现
class ConfigManager:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def __init__(self):
if not hasattr(self, 'initialized'):
self.settings = {}
self.initialized = True
# 使用
config1 = ConfigManager.get_instance()
config2 = ConfigManager.get_instance()
print(config1 is config2) # True
选择建议
-
简单场景:使用模块方式最简单直接
-
需要继承:使用元类或装饰器
-
多线程环境:使用线程安全版本
-
需要控制初始化 :使用
__new__+__init__配合 -
共享状态而非单实例:使用 Borg 模式
最佳实践示例(综合版)
import threading
from typing import Any
class Singleton(type):
"""线程安全的单例元类"""
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args: Any, **kwargs: Any) -> Any:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class DatabaseConnection(metaclass=Singleton):
def __init__(self, connection_string: str = ""):
if not hasattr(self, '_initialized'):
self.connection_string = connection_string
self._initialized = True
print(f"Database connection established: {connection_string}")
def query(self, sql: str):
print(f"Executing: {sql}")
# 使用
db1 = DatabaseConnection("mysql://localhost:3306")
db2 = DatabaseConnection("different_connection") # 第二个参数被忽略
print(db1 is db2) # True
选择哪种方式取决于具体需求,模块方式在大多数情况下是最简单、最 Pythonic 的实现。