引言
在完成了类与对象、继承与多态、封装与信息隐藏、特殊方法与运算符重载的学习后,今天我们深入 Python 面向对象编程的两个高级特性:属性描述符(Property Descriptors)和元类(Metaclass) 。
这两个特性是 Python 对象模型的基石,理解它们能让你真正掌握 Python 的"魔法"是如何实现的。
一、属性描述符(Property Descriptors)
1.1 什么是描述符?
描述符是实现了 __get__、__set__ 或 __delete__ 方法的对象。当描述符作为类属性时,它会拦截对该属性的访问。
python
class Descriptor:
def __get__(self, obj, objtype=None):
print(f"调用 __get__: obj={obj}, objtype={objtype}")
return self.value
def __set__(self, obj, value):
print(f"调用 __set__: obj={obj}, value={value}")
self.value = value
def __delete__(self, obj):
print(f"调用 __delete__: obj={obj}")
del self.value
class MyClass:
attr = Descriptor()
# 使用示例
obj = MyClass()
obj.attr = 100 # 触发 __set__
print(obj.attr) # 触发 __get__
输出:
vbnet
调用 __set__: obj=<__main__.MyClass object at 0x...>, value=100
调用 __get__: obj=<__main__.MyClass object at 0x...>, objtype=<class '__main__.MyClass'>
100
1.2 实战:类型检查描述符
描述符最常见的用途是实现类型检查和数据验证:
python
class Typed:
"""类型检查描述符"""
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(f"{self.name} 必须是 {self.expected_type.__name__} 类型")
obj.__dict__[self.name] = value
def __delete__(self, obj):
obj.__dict__.pop(self.name, None)
class Person:
name = Typed('name', str)
age = Typed('age', int)
email = Typed('email', str)
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def __repr__(self):
return f"Person({self.name!r}, {self.age!r}, {self.email!r})"
# 使用示例
p = Person("Alice", 25, "alice@example.com")
print(p) # Person('Alice', 25, 'alice@example.com')
# 类型检查生效
try:
p.age = "not an integer" # 会抛出 TypeError
except TypeError as e:
print(f"错误:{e}")
输出:
arduino
Person('Alice', 25, 'alice@example.com')
错误:age 必须是 int 类型
1.3 @property 装饰器的本质
@property 其实就是描述符的语法糖:
python
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
"""获取摄氏温度"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""设置摄氏温度(不能低于绝对零度)"""
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
self._celsius = value
@property
def fahrenheit(self):
"""获取华氏温度(只读)"""
return self._celsius * 9/5 + 32
# 使用示例
t = Temperature(25)
print(f"摄氏:{t.celsius}°C")
print(f"华氏:{t.fahrenheit}°F")
t.celsius = 30 # OK
print(f"新摄氏:{t.celsius}°C")
try:
t.celsius = -300 # 会抛出 ValueError
except ValueError as e:
print(f"错误:{e}")
二、元类(Metaclass)基础
2.1 什么是元类?
元类是"类的类"。普通类创建实例,元类创建类。type 是 Python 的默认元类。
python
# 普通类创建实例
class MyClass:
pass
obj = MyClass() # MyClass 创建 obj
# 元类创建类
class MyMeta(type):
def __new__(mcs, name, bases, attrs):
print(f"创建类:{name}")
print(f"基类:{bases}")
print(f"属性:{list(attrs.keys())}")
return super().__new__(mcs, name, bases, attrs)
class MyClass(metaclass=MyMeta):
x = 1
def method(self):
pass
输出:
scss
创建类:MyClass
基类:()
属性:['__module__', '__qualname__', 'x', 'method']
2.2 元类的实际应用:单例模式
python
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 = "DB Connection Established"
def query(self, sql):
return f"执行查询:{sql}"
# 验证单例
db1 = Database()
db2 = Database()
print(f"db1 is db2: {db1 is db2}") # True
print(f"db1.connection: {db1.connection}")
print(db1.query("SELECT * FROM users"))
输出:
vbnet
db1 is db2: True
db1.connection: DB Connection Established
执行查询:SELECT * FROM users
2.3 元类实战:自动注册插件系统
python
class PluginRegistry(type):
"""插件注册元类"""
plugins = {}
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
if name != 'BasePlugin': # 不注册基类
plugin_name = attrs.get('plugin_name', name)
mcs.plugins[plugin_name] = cls
return cls
class BasePlugin(metaclass=PluginRegistry):
"""插件基类"""
plugin_name = "base"
def execute(self):
raise NotImplementedError
# 定义插件
class EmailPlugin(BasePlugin):
plugin_name = "email"
def execute(self):
return "发送邮件通知"
class SMSPlugin(BasePlugin):
plugin_name = "sms"
def execute(self):
return "发送短信通知"
class PushPlugin(BasePlugin):
plugin_name = "push"
def execute(self):
return "发送推送通知"
# 使用插件系统
print("已注册插件:", list(PluginRegistry.plugins.keys()))
# 动态调用插件
for name, plugin_cls in PluginRegistry.plugins.items():
plugin = plugin_cls()
print(f"[{name}] {plugin.execute()}")
输出:
css
已注册插件:['email', 'sms', 'push']
[email] 发送邮件通知
[sms] 发送短信通知
[push] 发送推送通知
三、描述符与元类的组合使用
将描述符和元类结合,可以创建强大的框架基础:
python
class Field:
"""字段描述符"""
def __init__(self, field_type=str, required=True):
self.field_type = field_type
self.required = required
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if self.required and value is None:
raise ValueError(f"{self.name} 是必填字段")
if not isinstance(value, self.field_type):
raise TypeError(f"{self.name} 必须是 {self.field_type.__name__} 类型")
obj.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = name
class ModelMeta(type):
"""模型元类"""
def __new__(mcs, name, bases, attrs):
cls = super().__new__(mcs, name, bases, attrs)
# 收集所有字段
cls._fields = {
k: v for k, v in attrs.items()
if isinstance(v, Field)
}
return cls
class Model(metaclass=ModelMeta):
"""ORM 模型基类"""
def __init__(self, **kwargs):
for field_name, field_obj in self._fields.items():
value = kwargs.get(field_name)
if field_obj.required and value is None:
raise ValueError(f"缺少必填字段:{field_name}")
setattr(self, field_name, value)
def to_dict(self):
return {
name: getattr(self, name)
for name in self._fields
}
# 定义用户模型
class User(Model):
username = Field(str, required=True)
email = Field(str, required=True)
age = Field(int, required=False)
# 使用示例
user = User(username="alice", email="alice@example.com", age=25)
print(f"用户数据:{user.to_dict()}")
try:
invalid_user = User(username="bob") # 缺少 email
except ValueError as e:
print(f"错误:{e}")
输出:
arduino
用户数据:{'username': 'alice', 'email': 'alice@example.com', 'age': 25}
错误:缺少必填字段:email
总结
| 特性 | 用途 | 关键方法 |
|---|---|---|
| 描述符 | 属性访问拦截、类型检查、验证 | __get__, __set__, __delete__ |
| 元类 | 类的创建、自动注册、框架设计 | __new__, __call__ |
__set_name__ |
描述符自动获取属性名 | Python 3.6+ |
核心要点:
- 描述符是属性访问的"守门人"
- 元类是类创建的"工厂"
- 两者结合可以构建强大的 ORM、验证框架等
- 日常开发优先使用
@property,复杂场景再考虑自定义描述符和元类