2017-07-30 73 views
23

我终于升级我的Python版本,我发现新功能的加入。除此之外,我正在摸索新的__init_subclass__方法。从文档:了解__init_subclass__

只要包含的类是子类,就会调用此方法。然后cls 是新的子类。如果被定义为一个正常的实例方法,该 方法隐式转换为一个类的方法。

于是我开始玩弄它一点点,下面的文档的例子:

class Philosopher: 
    def __init_subclass__(cls, default_name, **kwargs): 
     super().__init_subclass__(**kwargs) 
     print(f"Called __init_subclass({cls}, {default_name})") 
     cls.default_name = default_name 

class AustralianPhilosopher(Philosopher, default_name="Bruce"): 
    pass 

class GermanPhilosopher(Philosopher, default_name="Nietzsche"): 
    default_name = "Hegel" 
    print("Set name to Hegel") 

Bruce = AustralianPhilosopher() 
Mistery = GermanPhilosopher() 
print(Bruce.default_name) 
print(Mistery.default_name) 

产生这样的输出:

Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce') 
'Set name to Hegel' 
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche') 
'Bruce' 
'Nietzsche' 

据我所知,这种方法被称为子类定义之后,但我的问题是特别了解此功能的使用。我还阅读了PEP 487文章,但对我没有多大帮助。这种方法在哪里会有帮助?是否适用于:

  • 超类在创建时注册子类?
  • 迫使子类设置在定义时的字段?

此外,我是否需要了解__set_name__才能完全理解其用法?

回答

10

__init_subclass____set_name__正交机制 - 他们不依赖于对方,只是在同一PEP描述。两者都是以前需要全功能元类的功能。该PEP 487地址元类最常见的用途:

  • 如何让家长知道什么时候被子类(__init_subclass__
  • 如何让一个描述符类知道属性的名称,它它被用于(__set_name__

正如PEP说:

虽然有使用许多可能的方法元类的绝大多数的使用情况分为只是三类:类创作,描述符的初始化后运行,并保持在类属性被定义级的一些初始化代码。

前两类可容易地通过简单的具有钩入类创建来实现:

  • 一种__init_subclass__钩初始化给定类的所有子类。
  • 在类创建,一个__set_name__钩上调用中的所有类中定义的属性(描述符),和

第三类是另一个PEP,PEP 520的话题。

还要注意的是,尽管__init_subclass__是在描述符类使用元类在这个类的继承树,__set_name__的替代是使用元类为具有的一个实例类的替代品描述符作为属性

+0

我没有处理元类要充分理解它的用法,但要感谢你的解释(和其他人),看起来有很多情况需要“完成”声明后的子类的定义。 – EsotericVoid

15

PEP 487着手采用两种常见的元类使用情况,并使它们更易于访问,而无需了解元类的所有细节。这两个新功能__init_subclass____set_name__否则是独立,它们不互相依赖。

__init_subclass__只是一个钩子方法。你可以用它来做任何你想要的。这对于以某种方式注册子类很有用,用于设置这些子类的默认属性值。

最近,我们用它来为不同的版本控制系统提供“适配器”,例如:

class RepositoryType(Enum): 
    HG = auto() 
    GIT = auto() 
    SVN = auto() 
    PERFORCE = auto() 

class Repository(): 
    _registry = {t: {} for t in RepositoryType} 

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

class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'): 
    pass 

class GenericGitRepository(Repository, scm_type=RepositoryType.GIT): 
    pass 

这平凡,让我们定义特定仓库处理类,而不必诉诸使用元类或装饰。

+1

很好的例子。阅读其他答案,我知道这就像是扩展基类的功能而不必处理元类的快捷方式。 – EsotericVoid

+0

PEP建议'super().__ init_subclass __(cls,** kwargs)'应该改为'super().__ init_subclass __(** kwargs)'。我认为在类的上下文中,'super()'将绑定'cls'作为第一个参数。 –

+0

@Baioy:这是我的错误,谢谢指出! –

5

作为PEP的标题,__init_subclass__的主要观点是为类提供更简单的定制形式。

这是一个钩子,它允许您修改不需要了解元类的类,跟踪类构造的所有方面或担心元类冲突。作为a message由尼克·科格伦这个PEP状态的早期阶段:

主要预期可读性/可维护性的好处是从更清楚区分的 视角从“customises运行时行为“customises子 初始化”案 子类“案例。

完全自定义的元类不提供 影响范围的迹象,而__init_subclass__更清楚地表明,有对行为后的子类创建没有 持久的影响。

元类被认为是有魔力的原因,你不知道在创建类之后他们的效果会是什么。另一方面,__init_subclass__只是另一个类方法,它只运行一次,然后完成。 (see its documentation for exact functionality.)


PEP 487的整个点为约简化(即不再需要使用)元类对于一些常见用途。

__init_subclass__负责后级初始化,而__set_name__(仅对描述符类有意义)被添加以简化初始化描述符。除此之外,他们不相关。

提到的元类(保持定义顺序)的第三种常见情况是was also simplified。这是写给瓦特/ OA挂钩,通过使用有序映射命名空间(这在Python 3.6为dict,但是这是一个实现细节:-)

1

我想补充相关元类和__init_subclass__一些引用可能会有所帮助。

背景

__init_subclass__引入作为替代创建元类。 这是一个核心开发人员Brett Cannon在talk中的PEP 487的2分钟摘要。

参考教材

  • 吉多·范罗苏姆的blog post对元类的早期历史在Python
  • 杰克Vanderplas的blog post期待更深入的贯彻元类