了解初始子类__

2024-05-19 22:26:34 发布

您现在位置:Python中文网/ 问答频道 /正文

我最终升级了python版本,发现了新增加的特性。除此之外,我还在为新的^{}方法绞尽脑汁。从文档中:

This method is called whenever the containing class is subclassed. cls is then the new subclass. If defined as a normal instance method, this method is implicitly converted to a class method.

因此,我开始按照文档中的示例来处理它:

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这篇文章,但没给我多少帮助。这种方法有什么帮助?是为了:

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

另外,我需要理解^{}才能完全理解它的用法吗?


Tags: to方法namedefaultinitismethodclass
3条回答

__init_subclass____set_name__是正交的机制-它们不是相互联系的,只是在同一个PEP中描述的。这两个特性以前都需要一个功能齐全的元类。PEP 487解决了元类最常用的2问题:

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

正如政治公众人物所说:

While there are many possible ways to use a metaclass, the vast majority of use cases falls into just three categories: some initialization code running after class creation, the initialization of descriptors and keeping the order in which class attributes were defined.

The first two categories can easily be achieved by having simple hooks into the class creation:

  • An __init_subclass__ hook that initializes all subclasses of a given class.
  • upon class creation, a __set_name__ hook is called on all the attribute (descriptors) defined in the class, and

The third category is the topic of another PEP, PEP 520.

注意,虽然__init_subclass__是在中使用元类的替换,但这个类的继承树中,__set_name__是在描述符类中使用元类的替换,该类具有描述符的实例作为属性

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

这使得我们可以为特定的存储库定义处理程序类,而不必使用元类或装饰器。

正如PEP的标题所示,__init_subclass__的主要目的是为类提供一种更简单的定制形式。

它是一个钩子,允许你在不需要了解元类、跟踪类构造的所有方面或担心元类冲突的情况下修改类。正如Nick Coghlan在本政治公众人物早期阶段所说:

The main intended readability/maintainability benefit is from the perspective of more clearly distinguishing the "customises subclass initialisation" case from the "customises runtime behaviour of subclasses" case.

A full custom metaclass doesn't provide any indication of the scope of impact, while __init_subclass__ more clearly indicates that there's no persistent effects on behaviour post-subclass creation.

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


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

__init_subclass__负责类后初始化,同时添加__set_name__(这只对描述符类有意义)以简化描述符的初始化。除此之外,他们没有亲戚关系。

第三种常见的元类情况(保持定义顺序)是was also simplified。这是通过使用命名空间的有序映射(在Python3.6中是一个dict,但这是一个实现细节:-)

相关问题 更多 >