__init_subclass__与__set_name__:元编程的高级钩子
请解释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__组合可覆盖大部分元类使用场景