CodeWalk

__init_subclass__与__set_name__:元编程的高级钩子

作者:Yahuda · 2026-05-30 12:55

请解释Python中__init_subclass____set_name__这两个相对较新的元编程钩子的作用和使用场景。与元类(Metaclass)相比,它们各自能解决什么问题?给出用__init_subclass__实现类注册表的示例,以及用__set_name__实现描述符自动获取属性名的示例。

回答

Yahuda

__init_subclass__(Python 3.6+):在父类定义,子类被创建时触发,替代部分元类场景。

class PluginBase:
    _registry = {}
    
    def __init_subclass__(cls, name=None, **kwargs):
        super().__init_subclass__(**kwargs)
        if name is not None:
            PluginBase._registry[name] = cls

class LogPlugin(PluginBase, name='log'):  # 自动注册
    pass

class EmailPlugin(PluginBase, name='email'):
    pass

print(PluginBase._registry)  # {'log': LogPlugin, 'email': EmailPlugin}

__set_name__(Python 3.6+):在包含类创建时,描述符的__set_name__被调用,自动获得属性名。

class ValidatedField:
    def __set_name__(self, owner, name):
        self._name = name  # 自动获取字段名
        
    def __get__(self, obj, objtype=None):
        return obj.__dict__.get(self._name)
        
    def __set__(self, obj, value):
        if not isinstance(value, str):
            raise TypeError(f'{self._name} must be a string')
        obj.__dict__[self._name] = value

class User:
    name = ValidatedField()  # 自动知道字段名是'name'
    email = ValidatedField()

与元类对比

  • __init_subclass__:解决了元类最常见的子类注册需求,无需元类
  • __set_name__:解决了描述符必须硬编码或不安全地检查__dict__的问题
  • 元类仍然需要:修改类创建过程、拦截属性访问、动态修改类的所有子类行为
  • __init_subclass__ + __set_name__ 组合可覆盖大部分元类使用场景