跳到主要内容

元编程

元编程(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__

setattrdelattr

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 时:

  1. 如果 attr 在实例的 __dict__ 中,直接返回(实例属性优先)
  2. 否则,如果 attr 在类的 __dict__ 中且是描述符,调用 __get__
  3. 否则,返回类的 __dict__['attr']

当赋值 instance.attr = value 时:

  1. 如果 attr 在类的 __dict__ 中且是覆盖型描述符(实现了 __set__),调用 __set__
  2. 否则,存入实例的 __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) 可以动态创建类
  • 元类很强大,但大多数情况下类装饰器或普通继承就够了