为什么 Python 中的泛型在元类上使用 __class_getitem__ 而不是 __getitem__ 来实现

use*_*012 9 python

我正在阅读 python 文档和 peps,但找不到答案。

python中的泛型是通过给类对象添加下标来实现的。list[str]是一个列表,其中所有元素都是字符串。
此行为是通过实现一个特殊的(dunder)类方法来实现的,__class_getitem__根据文档所述,该方法应返回 GenericAlias。

一个例子:

class MyGeneric:
    def __class_getitem__(cls, key):
        # implement generics
        ...
Run Code Online (Sandbox Code Playgroud)

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

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

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


class MyGeneric(metaclass=GenericMeta):
    ...
Run Code Online (Sandbox Code Playgroud)

稍后文档还显示了 s 的示例,Enum使用元类的 a作为 a未被调用的__getitem__示例。__class_getitem__

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

它似乎与元类做了完全相同的事情,__getitem__但增加了复杂性,并且需要在解释器中添加额外的代码来决定调用哪个方法。所有这些都没有额外的好处,因为定义两者每次都会简单地调用相同的方法,除非专门调用 dunder 方法(通常不应该这样做)。

我知道不鼓励以这种方式实现泛型。__class_getitem__一般方法是对已经定义类似的类进行子类化typing.Generic,但我仍然很好奇为什么以这种方式实现该功能。

Mar*_*ers 13

__class_getitem__之所以存在,是因为在涉及多个元类的情况下使用多重继承非常棘手,并且设置了使用第三方库时始终无法满足的限制。

\n

如果没有__class_getitem__泛型,则需要元类,因为 __getitem__在类上定义方法只会处理实例上的属性访问,而不是类上的属性访问。通常,语法由的类型object[...]处理,而不是由其本身处理。例如,这是类,但对于来说,这是元类。objectobject

\n

所以,语法:

\n
ClassObject[some_type]\n
Run Code Online (Sandbox Code Playgroud)\n

将翻译为:

\n
type(ClassObject).__getitem__(ClassObject, some_type)\n
Run Code Online (Sandbox Code Playgroud)\n

__class_getitem__存在是为了避免必须为每个需要支持泛型的类提供一个元类。

\n

有关__getitem__其他特殊方法的工作原理,请参阅Python 数据模型章节中的特殊方法查找部分:

\n
\n

对于自定义类,只有在 object\xe2\x80\x99s 类型上定义特殊方法的隐式调用才能保证正确工作,而不是在 object\xe2\x80\x99s 实例字典中定义。

\n
\n

同一章还明确涵盖了__class_getitem__vs__getitem__

\n
\n

通常,使用方括号订阅对象将调用__getitem__()object\xe2\x80\x99s 类上定义的实例方法。然而,如果被订阅的对象本身就是一个类,则__class_getitem__()可以调用类方法。

\n
\n

本节还介绍了如果类同时具有带方法的元类__getitem____class_getitem__本身定义的方法时会发生什么情况。您找到了本节,但它仅适用于这个特定的极端情况

\n

如前所述,使用元类可能很棘手,尤其是从具有不同元类的类继承时。请参阅原始PEP 560 -对类型模块和泛型类型的核心支持提案

\n
\n

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

\n

...

\n

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

\n
\n

当将多个类与不同的元类混合时,Python 要求最具体的元类派生自其他元类,如果该元类不是您自己的元类,则很难满足这一要求;请参阅有关确定适当元类的文档

\n

作为旁注,如果您确实使用元类,那么__getitem__应该是类方法:

\n
class GenericMeta(type):\n    # not a classmethod! `self` here is a class, an instance of this\n    # metaclass.\n    def __getitem__(self, key):\n        # implement generics\n        ...\n
Run Code Online (Sandbox Code Playgroud)\n

在 PEP 560 之前,这基本上就是typing.GenericMeta元类所做的事情,尽管稍微复杂一些。

\n