我正在阅读Python文档和PEPS,但找不到答案.

Generics in python are implemented by subscripting class objects. list[str] is a list where all elements are integers.
This behaviour is achieved by implementing a special (dunder) classmethod called __class_getitem__ which as the documentation states should return a GenericAlias.

举个例子:

class MyGeneric:
    def __class_getitem__(cls, key):
        # implement generics
        ...

这对我来说似乎很奇怪,因为文档还显示了一些代码,类似于面对下标对象时解释器所做的事情,并显示在对象的元类上定义__getitem__和在对象本身上定义__class_getitem__总是 Select 元类‘__getitem__.这意味着无需在语言中引入新的特殊方法即可实现与上面的功能相同的类.

具有相同行为的类的一个示例:

class GenericMeta(type):
    def __getitem__(self, key):
        # implement generics
        ...


class MyGeneric(metaclass=GenericMeta):
    ...

稍后,文档还显示了一个使用元类的__getitem__作为未被调用的__class_getitem__的示例的Enum.

我的问题是,为什么首先引入__class_getitem__类方法?

它似乎做了与元类‘__getitem__完全相同的事情,但增加了复杂性,并且需要在解释器中编写额外的代码来决定调用哪个方法.所有这些都没有额外的好处,因为除非专门调用DUnder方法(一般不应该这样做),否则每次定义两者都只会调用相同的方法.

我知道以这种方式实现泛型是不受欢迎的.通常的做法是将已经定义了__class_getitem__的类划分为子类,比如typing.Generic,但我仍然很好奇为什么会这样实现该功能.

推荐答案

简而言之:因为在类上定义的__getitem__方法处理属性访问on instances,而不是在类上.

通常,object[...]语法由object中的type处理,而不是由object本身处理.例如,这是类,但对于classes,这是metaclass.

因此,语法:

ClassObject[some_type]

将转化为:

type(ClassObject).__getitem__(ClassObject, some_type)

__class_getitem__的存在是为了避免必须 for each 需要支持泛型的类提供元类.

有关__getitem__和其他特殊方法的工作原理,请参阅Python Datamodel一章中的Special method lookup section:

对于定制类,只有在对象的类型上定义的特殊方法的隐式调用才能保证正确工作,而不是在对象的实例字典中定义.

同一章还明确涵盖了__class_getitem__ versus __getitem__项内容:

通常,使用方括号的对象订阅将调用在对象的类上定义的__getitem__()实例方法.然而,如果被订阅的对象本身是类,则可以改为调用类方法__class_getitem__().

本节还介绍了如果类本身定义了一个带有__getitem__方法的元类和and__class_getitem__方法的元类,将会发生什么.你找到了这一部分,但它是only applies in this specific corner-case.

添加__class_getitem__方法是因为使用元类可以达到tricky,特别是在从具有不同元类的类继承时.请参阅原文PEP 560 - Core support for typing module and generic types proposal:

所有泛型类型都是GenericMeta的实例,因此如果用户使用自定义元类,则很难使相应的类成为泛型.对于用户不能控制的库类来说,这尤其困难.

...

在建议的特殊属性的帮助下,将不再需要GenericMeta元类.

当混合具有不同元类的多个类时,Python要求最具体的元类派生自其他元类,如果该元类不是您自己的元类,则很难满足这一要求;请参见documentation on determining the appropriate metaclass.

顺便提一下,如果您确实使用元类,那么__getitem__应该是一个类方法:

class GenericMeta(type):
    # not a classmethod! `self` here is a class, an instance of this
    # metaclass.
    def __getitem__(self, key):
        # implement generics
        ...

在PEP 560之前,这一数字是basically what the typing.GenericMeta metaclass did,尽管有点复杂.

Python相关问答推荐

Python中的Pool.starmap异常处理

收件箱转换错误- polars.exceptions. ComputeHelp- pandera(0.19.0b3)带有polars

给定数据点,制定它们的关系

使用matplotlib pcolormesh,如何停止从一行绘制的磁贴连接到上下行?

Python -Polars库中的滚动索引?

如何使用矩阵在sklearn中同时对每个列执行matthews_corrcoef?

Polars:使用列值引用when / then表达中的其他列

如何将ctyles.POINTER(ctyles.c_float)转换为int?

ModuleNotFound错误:没有名为Crypto Windows 11、Python 3.11.6的模块

. str.替换pandas.series的方法未按预期工作

如何访问所有文件,例如环境变量

根据二元组列表在pandas中创建新列

在Wayland上使用setCellWidget时,try 编辑QTable Widget中的单元格时,PyQt 6崩溃

对所有子图应用相同的轴格式

pyscript中的压痕问题

将输入聚合到统一词典中

合并帧,但不按合并键排序

如何在Python中使用另一个数据框更改列值(列表)

ruamel.yaml dump:如何阻止map标量值被移动到一个新的缩进行?

pysnmp—lextudio使用next()和getCmd()生成器导致TypeError:tuple对象不是迭代器''