理解__init_subclass__

时间:2017-07-30 13:25:34

标签: python class subclass python-3.6 metaclass

我终于升级了我的python版本,我发现了添加的新功能。除此之外,我正在试着采用新的__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__以完全理解其用法?

4 个答案:

答案 0 :(得分:22)

PEP 487开始采用两种常见的元类用例,使其更易于访问,而无需了解元类的所有细节。这两个新功能__init_subclass____set_name__在其他地方独立,它们并不相互依赖。

__init_subclass__只是一个钩子方法。您可以将它用于任何您想要的任何东西。它对于以某种方式注册子类非常有用,用于在这些子类上设置默认属性值。

我们最近用它来提供适配器&#39;对于不同的版本控制系统,例如:

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 :(得分:18)

__init_subclass____set_name__是正交机制 - 它们没有相互关联,只是在同一个PEP中描述。两者都是之前需要功能齐全的元类的功能。 PEP 487解决了元类最常见用法的 2

  • 如何让父母知道它何时被子类化(__init_subclass__
  • 如何让描述符类知道它所用属性的名称(__set_name__

正如PEP所说:

  

虽然有许多可能的方法来使用元类,但绝大多数用例只分为三类:一些初始化代码在类创建后运行,初始化描述符并保持类属性的顺序定义

     

通过在类创建中使用简单的钩子可以很容易地实现前两个类别:

     
      
  • 初始化给定类的所有子类的__init_subclass__挂钩。
  •   
  • 在创建类时,在类中定义的所有属性(描述符)上调用__set_name__钩子,并且
  •   
     

第三类是另一个PEP的主题PEP 520

另请注意,虽然__init_subclass__类的继承树中使用元类的替代,但描述符类中的__set_name__是替换为使用具有描述符实例的类的元类的元类

答案 2 :(得分:5)

正如PEP的标题所暗示的那样,__init_subclass__的要点是为课程提供更简单的自定义形式。

它是一个钩子,允许你修改类,无需了解元类,跟踪类构造的所有方面或担心元类冲突。在本PEP的早期阶段由Nick Coghlan提出a message

  

主要的预期可读性/可维护性优势来自于   更清楚地区分“定制子类”的视角   初始化“来自”自定义运行时行为的情况   子类“case。

     

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

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

PEP 487的重点是简化(即不再使用)元类用于某些常见用途。

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

提到的元类的第三个常见情况(保持定义顺序),was also simplified。这是通过使用命名空间的有序映射(在Python 3.6中是dict而没有钩子来解决的,但这是一个实现细节: - )

答案 3 :(得分:1)

我想添加一些与元类相关的引用和__init_subclass__可能有用的参考。

背景

引入了

__init_subclass__作为创建元类的替代方法。 以下是其中一位核心开发人员Brett Cannon对PEP 487 talk的2分钟摘要。

推荐参考资料

  • Guido van Rossum关于Python中元类的早期历史的blog post
  • Jake Vanderplas的blog post更深入地研究实现元类