07 - 元类与描述符¶
学习时间: 90 分钟 重要性: ⭐⭐⭐ 深入理解 Python 对象模型
🎯 学习目标¶
- 理解 Python 的对象模型和类的创建过程
- 掌握元类的基本概念和使用方法
- 理解描述符协议及其应用场景
- 知道何时(以及何时不要)使用这些高级特性
🧠 元类( Metaclass )¶
什么是元类¶
在 Python 中,一切皆对象——包括类本身。类是由元类创建的对象。
Python
# Python 3.10+ 语法
# 类的类型是什么?
class MyClass:
pass
print(type(MyClass)) # <class 'type'>
print(type(int)) # <class 'type'>
print(type(str)) # <class 'type'>
# type 是所有类的默认元类
# type 本身也是一个类,它的类型是它自己
print(type(type)) # <class 'type'>
类创建的本质:
Python
# 这两种定义方式是等价的:
# 方式一:class 语句
class Dog:
species = "Canis familiaris"
def __init__(self, name: str):
self.name = name
def bark(self) -> str:
return f"{self.name} says woof!"
# 方式二:直接调用 type()
Dog = type(
"Dog", # 类名
(), # 基类元组
{ # 类属性字典
"species": "Canis familiaris",
"__init__": lambda self, name: setattr(self, "name", name),
"bark": lambda self: f"{self.name} says woof!",
}
)
定义自定义元类¶
Python
# 自定义元类继承自 type
class MyMeta(type):
def __new__(mcs, name: str, bases: tuple, namespace: dict):
"""
创建类对象
参数:
mcs: 元类本身
name: 新类的名称
bases: 基类元组
namespace: 类的命名空间字典(包含属性和方法)
"""
print(f"Creating class: {name}")
print(f"Bases: {bases}")
print(f"Namespace keys: {list(namespace.keys())}")
# 调用父类 (type) 的 __new__ 创建类对象
cls = super().__new__(mcs, name, bases, namespace)
return cls
def __init__(cls, name: str, bases: tuple, namespace: dict):
"""初始化类对象(在 __new__ 之后调用)"""
print(f"Initializing class: {name}")
super().__init__(name, bases, namespace)
def __call__(cls, *args, **kwargs):
"""当类被调用时(即创建实例时)"""
print(f"Creating instance of {cls.__name__}")
instance = super().__call__(*args, **kwargs)
return instance
# 使用自定义元类
class MyClass(metaclass=MyMeta):
class_attr = "hello"
def __init__(self, value: int):
self.value = value
# 输出:
# Creating class: MyClass
# Bases: ()
# Namespace keys: ['__module__', '__qualname__', 'class_attr', '__init__']
# Initializing class: MyClass
obj = MyClass(42)
# 输出:
# Creating instance of MyClass
实用元类示例:单例模式¶
Python
class SingletonMeta(type):
"""单例元类:确保类只有一个实例"""
_instances: dict[type, object] = {}
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 Database(metaclass=SingletonMeta):
def __init__(self, connection_string: str):
self.connection_string = connection_string
print(f"Connecting to {connection_string}")
# 测试单例
db1 = Database("postgresql://localhost/mydb") # 打印连接信息
db2 = Database("postgresql://localhost/other") # 不打印,返回已有实例
print(db1 is db2) # True
print(db1.connection_string) # postgresql://localhost/mydb
实用元类示例:自动注册¶
Python
class PluginRegistry(type):
"""插件注册元类:自动收集所有插件类"""
plugins: dict[str, type] = {}
def __new__(mcs, name: str, bases: tuple, namespace: dict):
cls = super().__new__(mcs, name, bases, namespace)
# 不注册基类
if bases: # 只有子类才注册
mcs.plugins[name] = cls
print(f"Registered plugin: {name}")
return cls
@classmethod
def get_plugin(mcs, name: str) -> type | None:
return mcs.plugins.get(name)
class BasePlugin(metaclass=PluginRegistry):
"""插件基类"""
def execute(self):
raise NotImplementedError
class JsonPlugin(BasePlugin):
def execute(self):
return "Processing JSON"
class XmlPlugin(BasePlugin):
def execute(self):
return "Processing XML"
# 输出:
# Registered plugin: JsonPlugin
# Registered plugin: XmlPlugin
# 使用插件
print(PluginRegistry.plugins)
# {'JsonPlugin': <class 'JsonPlugin'>, 'XmlPlugin': <class 'XmlPlugin'>}
plugin_cls = PluginRegistry.get_plugin("JsonPlugin")
if plugin_cls:
plugin = plugin_cls()
print(plugin.execute()) # Processing JSON
⚠️ 何时使用元类¶
适合使用元类的场景: - 框架开发(如 Django ORM 、 SQLAlchemy ) - 插件系统和自动注册 - 强制 API 规范 - 单例等设计模式
不建议使用元类的场景: - 简单的类定制(使用类装饰器更简单) - 可以用 __init_subclass__ 解决的问题( Python 3.6+)
Python
# Python 3.6+ 的 __init_subclass__ 是更简单的替代方案
class BasePlugin:
plugins: dict[str, type] = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BasePlugin.plugins[cls.__name__] = cls
print(f"Registered: {cls.__name__}")
class JsonPlugin(BasePlugin):
pass # 自动注册
class XmlPlugin(BasePlugin):
pass # 自动注册
print(BasePlugin.plugins)
# {'JsonPlugin': <class 'JsonPlugin'>, 'XmlPlugin': <class 'XmlPlugin'>}
🔧 描述符( Descriptor )¶
什么是描述符¶
描述符是实现了描述符协议的对象,用于自定义属性的访问、设置和删除行为。
描述符协议: - __get__(self, obj, objtype=None) - 获取属性时调用 - __set__(self, obj, value) - 设置属性时调用 - __delete__(self, obj) - 删除属性时调用 - __set_name__(self, owner, name) - 描述符被赋值给类属性时调用( Python 3.6+)
Python
class Descriptor:
"""描述符协议示例"""
def __set_name__(self, owner: type, name: str):
"""当描述符被赋值给类属性时调用"""
print(f"Descriptor assigned to {owner.__name__}.{name}")
self.name = name
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
"""获取属性值"""
if obj is None:
# 通过类访问(如 MyClass.attr)
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
"""设置属性值"""
print(f"Setting {self.name} = {value}")
setattr(obj, self.private_name, value)
def __delete__(self, obj):
"""删除属性"""
print(f"Deleting {self.name}")
delattr(obj, self.private_name)
class MyClass:
attr = Descriptor() # 触发 __set_name__
def __init__(self, value: int):
self.attr = value # 触发 __set__
# 输出:
# Descriptor assigned to MyClass.attr
obj = MyClass(42)
# 输出:
# Setting attr = 42
print(obj.attr) # 触发 __get__,输出: 42
实用描述符示例:类型验证¶
Python
class Typed:
"""类型验证描述符"""
def __init__(self, expected_type: type):
self.expected_type = expected_type
def __set_name__(self, owner: type, name: str):
self.name = name
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(
f"{self.name} must be {self.expected_type.__name__}, "
f"got {type(value).__name__}"
)
setattr(obj, self.private_name, value)
class Person:
name = Typed(str)
age = Typed(int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
# 正常使用
person = Person("Alice", 25)
print(person.name, person.age) # Alice 25
# 类型错误
try:
person.age = "not an int"
except TypeError as e:
print(e) # age must be int, got str
实用描述符示例:范围验证¶
Python
class Range:
"""范围验证描述符"""
def __init__(self, min_value: float | None = None, max_value: float | None = None):
self.min_value = min_value
self.max_value = max_value
def __set_name__(self, owner: type, name: str):
self.name = name
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
if self.min_value is not None and value < self.min_value:
raise ValueError(f"{self.name} must be >= {self.min_value}")
if self.max_value is not None and value > self.max_value:
raise ValueError(f"{self.name} must be <= {self.max_value}")
setattr(obj, self.private_name, value)
class Product:
price = Range(min_value=0)
quantity = Range(min_value=0, max_value=10000)
discount = Range(min_value=0, max_value=1)
def __init__(self, price: float, quantity: int, discount: float = 0):
self.price = price
self.quantity = quantity
self.discount = discount
# 正常使用
product = Product(99.99, 100, 0.1)
# 验证错误
try:
product.price = -10
except ValueError as e:
print(e) # price must be >= 0
try:
product.discount = 1.5
except ValueError as e:
print(e) # discount must be <= 1
组合描述符:构建验证系统¶
Python
from abc import ABC, abstractmethod
class Validator(ABC):
"""验证器基类"""
def __set_name__(self, owner: type, name: str):
self.name = name
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
self.validate(value)
setattr(obj, self.private_name, value)
@abstractmethod
def validate(self, value) -> None:
"""子类实现具体验证逻辑"""
pass
class String(Validator):
def __init__(self, min_length: int = 0, max_length: int | None = None):
self.min_length = min_length
self.max_length = max_length
def validate(self, value) -> None:
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if len(value) < self.min_length:
raise ValueError(f"{self.name} must be at least {self.min_length} characters")
if self.max_length and len(value) > self.max_length:
raise ValueError(f"{self.name} must be at most {self.max_length} characters")
class PositiveNumber(Validator):
def validate(self, value) -> None:
if not isinstance(value, (int, float)):
raise TypeError(f"{self.name} must be a number")
if value <= 0:
raise ValueError(f"{self.name} must be positive")
class Email(Validator):
def validate(self, value) -> None:
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if "@" not in value or "." not in value.split("@")[-1]:
raise ValueError(f"{self.name} must be a valid email address")
class User:
username = String(min_length=3, max_length=20)
email = Email()
age = PositiveNumber()
def __init__(self, username: str, email: str, age: int):
self.username = username
self.email = email
self.age = age
# 使用
user = User("alice", "alice@example.com", 25)
try:
user.username = "ab" # 太短
except ValueError as e:
print(e)
try:
user.email = "invalid-email" # 无效邮箱
except ValueError as e:
print(e)
数据描述符 vs 非数据描述符¶
Python
# 数据描述符:实现了 __get__ 和 __set__(或 __delete__)
# 非数据描述符:只实现了 __get__
class DataDescriptor:
"""数据描述符"""
def __get__(self, obj, objtype=None):
return "data descriptor get"
def __set__(self, obj, value):
pass # 即使不做任何事,也算数据描述符
class NonDataDescriptor:
"""非数据描述符"""
def __get__(self, obj, objtype=None):
return "non-data descriptor get"
class MyClass:
data = DataDescriptor()
non_data = NonDataDescriptor()
obj = MyClass()
# 数据描述符优先级高于实例属性
obj.__dict__["data"] = "instance value"
print(obj.data) # "data descriptor get" (描述符优先)
# 实例属性优先级高于非数据描述符
obj.__dict__["non_data"] = "instance value"
print(obj.non_data) # "instance value" (实例属性优先)
属性查找优先级: 1. 数据描述符 2. 实例属性(obj.__dict__) 3. 非数据描述符 4. 类属性 5. __getattr__(如果定义了)
🎯 最佳实践¶
元类 vs 类装饰器 vs __init_subclass__¶
Python
# 根据需求选择合适的工具:
# 1. 简单的类修改 → 类装饰器
def add_repr(cls):
def __repr__(self):
attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
return f"{cls.__name__}({attrs})"
cls.__repr__ = __repr__
return cls
@add_repr
class Point:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
# 2. 子类自动处理 → __init_subclass__
class BaseModel:
_registry = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
BaseModel._registry[cls.__name__] = cls
# 3. 深度定制类创建行为 → 元类
# 仅在需要在类创建过程中进行复杂操作时使用
描述符 vs property¶
Python
# property 适合单个类的简单属性逻辑
class Circle:
def __init__(self, radius: float):
self._radius = radius
@property
def radius(self) -> float:
return self._radius
@radius.setter
def radius(self, value: float):
if value < 0:
raise ValueError("Radius must be non-negative")
self._radius = value
# 描述符适合可复用的属性逻辑
class NonNegative:
def __set_name__(self, owner, name):
self.name = name
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, 0)
def __set__(self, obj, value):
if value < 0:
raise ValueError(f"{self.name} must be non-negative")
setattr(obj, self.private_name, value)
class Rectangle:
width = NonNegative()
height = NonNegative()
def __init__(self, width: float, height: float):
self.width = width
self.height = height
✅ 本章小结¶
| 概念 | 用途 | 复杂度 |
|---|---|---|
| 元类 | 控制类的创建过程 | ⭐⭐⭐⭐⭐ |
__init_subclass__ | 简化的子类注册 | ⭐⭐ |
| 类装饰器 | 类创建后的修改 | ⭐⭐ |
| 描述符 | 自定义属性访问 | ⭐⭐⭐ |
| property | 单属性的简单逻辑 | ⭐ |
记住: - 元类是强大但复杂的工具,优先考虑更简单的替代方案 - 描述符非常适合创建可复用的验证逻辑 - property 是最轻量的属性定制方式
📌 下一步:学习 06-并发与异步编程 掌握 Python 的并发模型。