E.B.4 元编程

元编程是用代码组织或生成代码结构。日常 Python 工程里最有用的元编程,通常不是炫技,而是自动注册、字段校验和减少重复模板。
- Python 3.10+
- 不需要第三方包
- 熟悉类的基本写法
- Registry(注册表):记录可用实现的映射表。
- 装饰器注册:用装饰器把类或函数加入注册表。
- Descriptor(描述符):控制属性读取和写入行为的对象。
__set_name__:让描述符知道自己被赋给了哪个属性名。- 动态类:运行时创建的类,常见方式是
type。
运行注册表和描述符示例
Section titled “运行注册表和描述符示例”创建 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)运行:
python metaprogramming_demo.py预期输出:
json rows['csv', 'json']daily-importerror: name cannot be empty注册表省掉了手写映射表。描述符把字段校验放在字段定义附近。
什么时候值得用
Section titled “什么时候值得用”适合:
- 很多类需要自动注册。
- 框架要发现插件。
- 很多字段共享同一类校验行为。
- 配置需要生成重复结构。
如果普通类或普通字典更清楚,就不要强行元编程。
魔法边界复盘
Section titled “魔法边界复盘”元编程只有在行为仍然容易发现时才值得保留。使用注册表、动态导入或描述符前,先写清楚队友如何查看可用实现,如何禁用一个坏实现,以及如何定位实际被调用的 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",确认会抛出清楚的异常。重点不是炫技,而是把重复字段校验规则放回字段定义附近。