理解__init_subclass__

Eso*_*oid 41 python metaclass class subclass python-3.6

我终于升级了我的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)
Run Code Online (Sandbox Code Playgroud)

生成此输出:

Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'
Run Code Online (Sandbox Code Playgroud)

我知道这个方法是子类定义之后调用的,但我的问题特别是关于这个特性的用法.我也阅读了PEP 487文章,但对我没什么帮助.这种方法在哪里有用?是为了:

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

另外,我是否需要了解它__set_name__以充分理解其用法?

Mar*_*ers 30

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
Run Code Online (Sandbox Code Playgroud)

这简单地让我们为特定的存储库定义处理程序类,而不必诉诸使用元类或装饰器.

  • 很好的例子。也阅读其他答案,我明白这就像扩展基类功能的捷径,而无需处理元类。 (4认同)
  • @JulianCamilleri:不,关键是`name` 参数是可选的。 (2认同)
  • 对于所有问题@MartijnPieters 深表歉意;很好奇这个阶段会发生什么:当“name”为“None”时,“cls._registry[scm_type][name] = cls” - 是否有某种我缺少的机制? (2认同)
  • 然后`None`是第二级的关键。这与字符串名称一样有效。因此,您可以在键“None”下注册一堆命名类和一个“默认”和未命名选项。 (2认同)

Ant*_*ala 25

__init_subclass__并且__set_name__是正交机制 - 它们没有相互联系,只是在同一个PEP中描述.两者都是之前需要功能齐全的元类的功能.该PEP 487地址2元类最常见的用途:

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

正如PEP所说:

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

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

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

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

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


pyl*_*ang 6

我想添加一些与元类相关的引用,__init_subclass__这可能会有所帮助。

背景

__init_subclass__被引入作为创建元类的替代方法。这是核心开发人员之一布雷特·坎农 (Brett Cannon)在一次演讲中PEP 487的 2 分钟摘要。

推荐参考

  • Guido van Rossum关于 Python 元类早期历史的博客文章
  • Jake Vanderplas 的博客文章更深入地探讨了元类的实现


Jim*_*ard 5

__init_subclass__正如PEP的标题所暗示的那样,主要观点是为课程提供更简单的定制形式.

它是一个钩子,允许你修改类,无需了解元类,跟踪类构造的所有方面或担心元类冲突.正如Nick Coghlan在本PEP早期阶段的消息所述:

主要的预期可读性/可维护性益处是从更清楚地区分"定制子类初始化"情况和"子类的定制运行时行为"情况的角度来看.

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

因为某种原因,元类被认为是神奇的,你不知道在创建类之后它们的效果是什么.__init_subclass__另一方面,它只是另一个类方法,它运行一次然后就完成了.(有关确切功能,请参阅其文档.)


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

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

还简化了提到的元类(保持定义顺序)的第三种常见情况.这是通过使用命名空间的有序映射(在Python 3.6中是一个dict,但这是一个实现细节:-)来解决的问题.