元类与单例模式
00:00
Python元类与单例模式详解。
概述
元类与单例模式是 Python 高级编程中的重要概念。元类控制类的创建过程,单例模式确保一个类只有一个实例。在 Python 中,元类是实现单例模式最优雅的方式之一。本文详细讲解元类的使用方法以及如何用元类和其他方式实现单例模式。
基础概念
元类的作用
元类是创建类的类。当 Python 解释器遇到 class 语句时,会调用元类来创建类对象。默认的元类是 type,我们可以自定义元类来拦截和修改类的创建过程。
单例模式的意义
单例模式确保一个类只有一个实例,并提供全局访问点。常见应用场景包括数据库连接池、配置管理器、日志记录器等需要全局共享状态的场景。
快速上手
元类基本用法
class ValidatedMeta(type):
"""在类创建时验证属性"""
def __new__(mcs, name, bases, namespace):
# 验证公开属性必须是简单类型
for key, value in namespace.items():
if key.startswith('_'):
continue
if not isinstance(value, (int, str, float)):
raise TypeError(f"{key} must be a simple type")
return super().__new__(mcs, name, bases, namespace)
class Config(metaclass=ValidatedMeta):
host = "localhost"
port = 8080
# debug = [] # TypeError!
单例模式实现
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 Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = connect()
详细用法
元类的三个钩子方法
class FullMeta(type):
"""展示元类的三个钩子方法"""
def __new__(mcs, name, bases, namespace):
"""创建类对象:可以修改类的属性和方法"""
print(f"1. __new__: 创建类 {name}")
# 可以在这里添加、删除或修改属性
namespace['auto_field'] = "由元类自动添加"
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace):
"""初始化类对象:可以执行额外的初始化逻辑"""
print(f"2. __init__: 初始化类 {name}")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
"""控制实例创建:可以拦截实例化过程"""
print(f"3. __call__: 创建 {cls.__name__} 的实例")
instance = super().__call__(*args, **kwargs)
return instance
class MyClass(metaclass=FullMeta):
def __init__(self):
self.value = 42
# 类创建时输出: 1. __new__ 和 2. __init__
obj = MyClass() # 输出: 3. __call__
print(obj.auto_field) # "由元类自动添加"
init_subclass 替代简单元类
Python 3.6 引入的 __init_subclass__ 可以替代许多简单元类场景,代码更简洁:
class Plugin:
"""使用 __init_subclass__ 实现自动注册"""
registry = {}
def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry[name or cls.__name__] = cls
class MySQLPlugin(Plugin, name="mysql"):
pass
class RedisPlugin(Plugin, name="redis"):
pass
print(Plugin.registry) # {'mysql': <class 'MySQLPlugin'>, 'redis': <class 'RedisPlugin'>}
单例模式的多种实现
方式一:模块级别变量
# config.py — 最 Pythonic 的单例
class _Config:
def __init__(self):
self.debug = False
self.host = "localhost"
# 模块只会被导入一次,天然单例
config = _Config()
方式二:使用 new
class Singleton:
_instance = None
_initialized = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
# 注意:__init__ 每次实例化都会被调用
if not self._initialized:
self.value = 0
self._initialized = True
方式三:元类实现
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:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
self.connection = "connected"
方式四:装饰器实现
def singleton(cls):
"""单例装饰器"""
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Config:
def __init__(self):
self.debug = False
config = Config() # 返回单例实例
单例的继承问题
# 元类实现的单例可以正确处理继承
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 Base(metaclass=SingletonMeta):
pass
class ChildA(Base):
pass
class ChildB(Base):
pass
a1 = ChildA()
a2 = ChildA()
b1 = ChildB()
print(a1 is a2) # True
print(a1 is b1) # False — 不同子类有不同实例
常见场景
场景一:全局配置管理
class ConfigMeta(type):
"""配置管理单例元类"""
_instance = None
def __call__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__call__(*args, **kwargs)
return cls._instance
class Config(metaclass=ConfigMeta):
def __init__(self):
self._data = {}
def get(self, key, default=None):
return self._data.get(key, default)
def set(self, key, value):
self._data[key] = value
def load_from_file(self, path):
import json
with open(path) as f:
self._data.update(json.load(f))
# 全局使用同一个配置实例
Config().set("debug", True)
print(Config().get("debug")) # True
场景二:自动注册的工厂
class AutoRegisterMeta(type):
"""自动注册子类的元类"""
_registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
# 不注册基类
if name != 'BaseHandler':
key = namespace.get('handler_name', name.lower())
mcs._registry[key] = cls
return cls
class BaseHandler(metaclass=AutoRegisterMeta):
def handle(self, data):
raise NotImplementedError
class CreateUserHandler(BaseHandler):
handler_name = "create_user"
def handle(self, data):
return f"创建用户: {data}"
class DeleteUserHandler(BaseHandler):
handler_name = "delete_user"
def handle(self, data):
return f"删除用户: {data}"
# 自动注册
handler = AutoRegisterMeta._registry["create_user"]()
print(handler.handle("Alice")) # 创建用户: Alice
注意事项
- 元类代码复杂,优先考虑
__init_subclass__、类装饰器或普通继承 - 单例模式使代码难以测试,因为状态在测试间共享。测试时需要提供重置机制
- 多线程环境下,单例实现需要加锁保证线程安全
- 单例违反了单一职责原则,过度使用会导致全局状态难以管理
- Python 的模块导入机制天然保证了模块级对象的单例性,这是最简单的实现方式
进阶用法
元类与描述符配合实现 ORM
class Column:
"""数据库列描述符"""
def __init__(self, col_type, primary_key=False):
self.col_type = col_type
self.primary_key = primary_key
def __set_name__(self, owner, name):
self.name = name
class ORMMeta(type):
"""ORM 元类:收集列信息"""
def __new__(mcs, name, bases, namespace):
columns = {}
for key, value in namespace.items():
if isinstance(value, Column):
columns[key] = value
namespace['_columns'] = columns
namespace['_table_name'] = name.lower()
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ORMMeta):
def save(self):
cols = ", ".join(self._columns.keys())
vals = ", ".join(f"'{getattr(self, k)}'" for k in self._columns)
return f"INSERT INTO {self._table_name} ({cols}) VALUES ({vals})"
class User(Model):
id = Column(int, primary_key=True)
name = Column(str)
email = Column(str)
u = User()
u.id = 1
u.name = "Alice"
u.email = "alice@example.com"
print(u.save()) # INSERT INTO user (id, name, email) VALUES ('1', 'Alice', 'alice@example.com')
可销毁的单例
class DestroyableSingleton(type):
"""可销毁的单例元类"""
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
def destroy(cls):
"""销毁单例实例(测试用)"""
if cls in cls._instances:
del cls._instances[cls]
class Service(metaclass=DestroyableSingleton):
def __init__(self):
self.started = True
# 测试时可以重置
def test_service():
Service.destroy() # 销毁旧实例
svc = Service() # 创建新实例
assert svc.started