理解 Python 中的 __init_subclass__
在Python的世界里,几乎所有的东西都是可变的。
起步
类方法 __init_subclass__
从 3.6
(whatsnew3.6) 引入,作用是可以在不使用元类的情况下改变子类的行为。也就是说它是独立于元类编程的,也能达到编辑其他类的一种手段。那么,如何来理解它呢?
从示例入手
按照文档中的示例:
class Philosopher:
def __init_subclass__(cls, default_name, **kwargs):
super().__init_subclass__(**kwargs)
cls.default_name = default_name
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
pass
示例中,当有子类继承了 Philosopher
,那么 __init_subclass__
就会调用。内容也很简单,父类 Philosopher
为它的所有子类都设置了 default_name 属性。
__init_subclass__
就像是个钩子函数,当子类定义之后触发。
默认实现 object.__init_subclass__
不执行任何操作。但默认实现不能传递任何参数,否则报错。
值得注意的是,示例中的 __init_subclass__
第一个参数是 cls
而不是常见的 self
。这是因为这个方法隐式地被 @classmethod
装饰(PEP-487)。这个决定其实是由于多数人忘记给 __prepare__
加上 @classmethod
装饰了,带来了不好的用户体验;而且在另一个PEP中 __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__
主要是解决了如何让描述符知道其属性的名称。
__init_subclass__
的目标是提供更简单的定制方式,在简单的场景下是元类的替代品。值得试一试。
转载自:https://juejin.cn/post/6844904065302790158