元编程
元编程(metaprogramming)是指用代码来操作代码的技术。Python 提供了一系列强大的工具来实现元编程:动态属性访问、属性描述符、类装饰器和元类。
动态属性访问
getattr
当访问一个不存在的属性时,Python 会调用 __getattr__:
class LazyDict:
def __init__(self, data):
self._data = data
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(f"'{self.__class__.__name__}' 没有属性 '{name}'")
>>> d = LazyDict({"name": "Alice", "score": 90})
>>> d.name
'Alice'
>>> d.score
90
注意:__getattr__ 只在属性不存在时被调用。如果属性存在,直接返回属性值。
getattribute
__getattribute__ 更强大(也更危险),它在每次访问属性时都会被调用:
class LoggingObject:
def __getattribute__(self, name):
print(f"正在访问: {name}")
return super().__getattribute__(name)
由于 __getattribute__ 拦截所有属性访问,如果实现不当很容易导致无限递归。通常优先使用 __getattr__,只有在确实需要拦截所有访问时才用 __getattribute__。
setattr 与 delattr
class FrozenObject:
def __setattr__(self, name, value):
if hasattr(self, name):
raise AttributeError(f"不能修改只读属性 '{name}'")
super().__setattr__(name, value)
def __delattr__(self, name):
raise AttributeError(f"不能删除属性 '{name}'")
>>> obj = FrozenObject()
>>> obj.x = 10
>>> obj.x = 20
AttributeError: 不能修改只读属性 'x'
特性 property
@property 让你把方法当作属性来访问:
class Member:
def __init__(self, name, scores):
self.name = name
self._scores = scores
@property
def average(self):
return sum(self._scores) / len(self._scores) if self._scores else 0
@property
def scores(self):
return tuple(self._scores) # 返回副本,防止外部修改
>>> m = Member("Alice", [85, 90, 88])
>>> m.average
87.66666666666667
>>> m.scores
(85, 90, 88)
setter 和 deleter
可以定义 setter 来控制属性的赋值:
class Member:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not value or len(value) < 2:
raise ValueError("名字太短")
self._name = value
@name.deleter
def name(self):
raise AttributeError("不能删除 name 属性")
>>> m = Member("Alice")
>>> m.name = "Bob"
>>> m.name = "A"
ValueError: 名字太短
属性描述符
描述符(descriptor)是实现了 __get__、__set__ 或 __delete__ 方法的类。property 本质上就是一个描述符。
非覆盖型描述符
只实现 __get__ 的描述符:
class NonNegative:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value < 0:
raise ValueError(f"{self.name} 不能为负数")
instance.__dict__[self.name] = value
class Product:
price = NonNegative("price")
quantity = NonNegative("quantity")
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
>>> p = Product("笔记本", 50, 10)
>>> p.price = -10
ValueError: price 不能为负数
描述符必须作为类属性定义。如果定义为实例属性,Python 不会把它当作描述符处理。
描述符的工作原理
当访问 instance.attr 时:
- 如果
attr在实例的__dict__中,直接返回(实例属性优先) - 否则,如果
attr在类的__dict__中且是描述符,调用__get__ - 否则,返回类的
__dict__['attr']
当赋值 instance.attr = value 时:
- 如果
attr在类的__dict__中且是覆盖型描述符(实现了__set__),调用__set__ - 否则,存入实例的
__dict__
这就是为什么描述符必须定义为类属性。
类装饰器
类装饰器和函数装饰器类似,它接收一个类并返回一个新的类(或修改后的原类):
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("初始化数据库连接")
>>> db1 = Database()
初始化数据库连接
>>> db2 = Database()
>>> db1 is db2
True
类装饰器是元类的一个轻量级替代方案。如果不需要控制类的创建过程,只想要在类创建后做一些修改,类装饰器通常更简单。
元类
元类(metaclass)是创建类的类。默认情况下,Python 用 type 来创建所有类。
type 构造函数
可以用 type(name, bases, namespace) 动态创建类:
>>> def greet(self):
... return f"Hello, {self.name}!"
...
>>> Person = type('Person', (), {'greet': greet})
>>> p = Person()
>>> p.name = 'Alice'
>>> p.greet()
'Hello, Alice!'
自定义元类
通过继承 type 可以自定义类的创建过程:
class AutoSlotMeta(type):
"""自动根据 __init__ 的参数生成 __slots__ 的元类"""
def __new__(mcs, name, bases, namespace):
if '__init__' in namespace:
import inspect
sig = inspect.signature(namespace['__init__'])
slots = [p.name for p in sig.parameters.values()
if p.name != 'self']
namespace['__slots__'] = slots
return super().__new__(mcs, name, bases, namespace)
class Member(metaclass=AutoSlotMeta):
def __init__(self, name, role, score=0):
self.name = name
self.role = role
self.score = score
>>> m = Member("Alice", "算法")
>>> m.__slots__
['name', 'role', 'score']
__new__ 在类创建之前被调用,用于控制类的创建;__init__ 在类创建之后被调用,用于初始化类对象。
prepare
Python 3 引入了 __prepare__ 方法,可以自定义类的命名空间(默认是普通字典):
class OrderedMeta(type):
@classmethod
def __prepare__(mcs, name, bases):
return {} # 可以返回 OrderedDict 来保持属性定义顺序
def __new__(mcs, name, bases, namespace):
print(f"创建类 {name},属性: {list(namespace.keys())}")
return super().__new__(mcs, name, bases, namespace)
什么时候用元类
绝大多数情况下,类装饰器或普通类继承就能解决问题。元类适合以下场景:
- API 框架中需要自动注册子类
- ORM 中需要自动映射类和数据库表
- 需要严格控制类的创建过程
记住 Python 之禅:"如果有一种明显的方法来做一件事,那应该只有一种方法"。元类很强大,但也很复杂,除非真的需要,否则不要用。
小结
__getattr__处理不存在的属性访问,__getattribute__拦截所有属性访问@property把方法变成属性,支持 getter、setter、deleter- 描述符是实现了
__get__/__set__/__delete__的类,是property的底层机制 - 类装饰器可以在不修改原类代码的情况下给类添加功能
- 元类控制类的创建过程,是"类的类"
type(name, bases, dict)可以动态创建类- 元类很强大,但大多数情况下类装饰器或普通继承就够了