这篇文章上次修改于 371 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
init__subclass
类方法 __init_subclass__
从 PEP 487 引入,作用是可以在不使用元类的情况下改变子类的行为(好!很有精神!)。也就是说它是独立于元类编程的,也能达到编辑其他类的一种手段。
从文档入手
class PluginBase:
subclasses = []
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
cls.subclasses.append(cls)
class Plugin1(PluginBase):
pass
class Plugin2(PluginBase):
pass
示例中,当有子类继承了 PluginBase
,那么 __init_subclass__
就会调用。内容也很简单,父类 PluginBase
将他们都存入了 subclasses
。
__init_subclass__
就像是个钩子函数,当子类定义之后触发。
默认实现 object.__init_subclass__
不执行任何操作。但默认实现不能传递任何参数,否则报错。
值得注意的是,示例中的 __init_subclass__
第一个参数是 cls 而不是常见的 self 。这是因为这个方法隐式地被 @classmethod
装饰(PEP-487)。这个决定其实是由于多数人忘记给 __prepare__
加上 @classmethod
装饰了,带来了不好的用户体验(这里);
而且在PEP 3115 中 __prepare__
被记录为普通的方法,所以已经不好改它了。而作为 3.6 新方法的 __init_subclass__
因此就有理由隐式装饰了(其实我比较喜欢显式,因为 Python 之禅中提到的 "显示优于隐式"。
尽管 __init_subclass__
是独立于元类编程的,但类都是由默认元类 type
创建的,那么在 type.__new__()
创建了类之后会有哪些步骤?
首先,type.__new__
收集类命名空间定义的 set_name()
方法的所有描述符;
其次,这些 __set_name__
的特定描述符在特定的情况下调用;
最后,在父类上调用钩子 __init_subclass__()
。
若类被装饰器装饰,那么就将上述生成的对象传递给类装饰器。
总的来说,__init_subclass__()
是钩子函数,它解决了如何让父类知道被继承的问题。钩子中能改变类的行为,而不必求助与元类或类装饰器。钩子用起来也更简单且容易理解。
虽然本文还提到了 __set_name__
,但它和 __init_subclass__
并不相互关联, __set_name__
主要是解决了如何让描述符知道其属性的名称。
一个简单Python插件系统
from enum import Enum
class PluginEnum(Enum):
Empty = empty = 0
Foo = foo = 1
class PluginBase:
subclasses = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 通过 cls.method 设置子类key
# 线性执行可以将 subclasses 设置为 list,cls.subclasses.append(cls)
cls.subclasses.setdefault(cls.method, cls)
def process_start(self):
# 插件执行前操作
pass
def process_run(self):
# 插件执行操作
pass
def process_end(self):
# 插件执行后操作
pass
def process_exception(self):
# 插件执行异常时异常处理
pass
def start(self):
# 插件调用流程
try:
self.process_start()
self.process_run()
self.process_end()
except Exception as e:
print(e)
self.process_exception()
class PluginCallableNone(PluginBase):
method = PluginEnum.Empty
def __init__(self, *args, **kwargs):
pass
def __call__(self, *args, **kwargs):
pass
class PluginFoo(PluginBase):
method = PluginEnum.Foo
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def process_start(self):
print("running in {} process_start".format(self.method))
def process_run(self):
print("running in {} process_run".format(self.method))
def process_end(self):
print("running in {} process_end".format(self.method))
def main():
# 获取所有子类(插件)
print("all subclasses: {}".format(PluginBase.subclasses))
# all subclasses: {<PluginEnum.Empty: 0>: <class '__main__.PluginCallableNone'>, <PluginEnum.Foo: 1>: <class '__main__.PluginFoo'>}
# 设置一个默认的可调用的None类,用于访问不存在的子类时的默认值
# 空插件实现
callable_none = PluginBase.subclasses.get(PluginEnum.Empty)
# 访问存在的子类(访问存在的插件)
print(f"foo is in PluginBase.subclasses: {PluginEnum.Foo in PluginBase.subclasses}")
# foo is in PluginBase.subclasses: True
PluginBase.subclasses.get(PluginEnum.Foo, callable_none)(foo="foo").start()
# running in foo process_start
# running in foo process_run
# running in foo process_end
# 访问不存在的子类(访问不存在的插件)
print("bar is in PluginBase.subclasses: {}".format("bar" in PluginBase.subclasses))
PluginBase.subclasses.get("bar", callable_none)(foo="foo").start()
# bar is in PluginBase.subclasses: False
if __name__ == "__main__":
main()
没有评论