跳转到内容

E.B.4 元编程

Python 元编程注册表图

元编程是用代码组织或生成代码结构。日常 Python 工程里最有用的元编程,通常不是炫技,而是自动注册、字段校验和减少重复模板。

  • Python 3.10+
  • 不需要第三方包
  • 熟悉类的基本写法
  • Registry(注册表):记录可用实现的映射表。
  • 装饰器注册:用装饰器把类或函数加入注册表。
  • Descriptor(描述符):控制属性读取和写入行为的对象。
  • __set_name__:让描述符知道自己被赋给了哪个属性名。
  • 动态类:运行时创建的类,常见方式是 type

创建 metaprogramming_demo.py

REGISTRY = {}
def register(name):
def decorator(cls):
REGISTRY[name] = cls
return cls
return decorator
@register("csv")
class CsvLoader:
def load(self):
return "csv rows"
@register("json")
class JsonLoader:
def load(self):
return "json rows"
class NonEmpty:
def __set_name__(self, owner, name):
self.private_name = "_" + name
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.private_name, None)
def __set__(self, instance, value):
if not value:
raise ValueError("name cannot be empty")
setattr(instance, self.private_name, value)
class JobConfig:
name = NonEmpty()
loader = REGISTRY["json"]()
print(loader.load())
print(sorted(REGISTRY))
config = JobConfig()
config.name = "daily-import"
print(config.name)
try:
config.name = ""
except ValueError as error:
print("error:", error)

运行:

Terminal window
python metaprogramming_demo.py

预期输出:

Terminal window
json rows
['csv', 'json']
daily-import
error: name cannot be empty

注册表省掉了手写映射表。描述符把字段校验放在字段定义附近。

适合:

  1. 很多类需要自动注册。
  2. 框架要发现插件。
  3. 很多字段共享同一类校验行为。
  4. 配置需要生成重复结构。

如果普通类或普通字典更清楚,就不要强行元编程。

元编程只有在行为仍然容易发现时才值得保留。使用注册表、动态导入或描述符前,先写清楚队友如何查看可用实现,如何禁用一个坏实现,以及如何定位实际被调用的 handler。

如果答案依赖隐藏导入副作用,这个抽象就太魔法了。优先保留显式名称、注册表打印结果,以及能证明输入选中正确实现的小测试。

交付检查时,打印注册表内容,并用两个不同输入各选中一个实现。再故意传入不存在的名字,确认错误信息清楚。元编程最需要这种检查,因为问题常常不是代码不能跑,而是运行时选错了对象。

如果检查结果让人看不懂,就减少魔法。显式字典、普通类和清晰函数名,很多时候比动态技巧更适合课程项目。

学完这一页,至少保留这张证据卡:

Python 模式
装饰器、迭代器、生成器、并发原语,或元编程钩子
代码产物
最小可运行示例加上打印输出
使用场景
这种模式在哪种 AI 应用、流水线、工具或服务器中更有用
失败检查
隐藏副作用、难读的抽象、竞态条件或过度设计
期望产出
带实际 AI 系统用途说明的小型高级 Python 示例
  • 为了显得高级而使用动态技巧。
  • 把行为藏得太深,导致调试痛苦。
  • 为了消除一点点重复,牺牲整体可读性。

加一个 yaml loader,确认 sorted(REGISTRY) 里包含它。然后为 retry_count 字段创建一个 IntegerRange(min_value, max_value) 描述符。

参考实现与讲解

yaml loader 应该通过同一套注册机制加入,所以不用手动维护另一张映射表,sorted(REGISTRY) 里也能看到 yaml

IntegerRange 描述符应该拒绝非整数,以及超出范围的整数。一个实用自检是先设置 retry_count = 3,再尝试 retry_count = -1"3",确认会抛出清楚的异常。重点不是炫技,而是把重复字段校验规则放回字段定义附近。