ZetCode

Python __class_getitem__ 方法

最后修改于 2025 年 4 月 8 日

本综合指南探讨了 Python 的 __class_getitem__ 方法,该方法在 PEP 560 中引入,用于类型提示和泛型类支持。我们将介绍基本用法、类型提示、泛型和实际示例。

基本定义

__class_getitem__ 方法允许类支持下标表示法(方括号),用于类型提示目的。 当使用 Class[item] 语法时,会调用它。

主要特征:它是一个类方法(虽然没有用 @classmethod 装饰),接收类作为第一个参数,并返回该类的专用版本。 它支持泛型类型注解,而无需运行时类型检查。

基本 __class_getitem__ 实现

这是一个简单的实现,展示了 __class_getitem__ 的工作方式。 这演示了该方法的基本语法和行为。

basic_class_getitem.py
class GenericBox:
    def __class_getitem__(cls, item):
        print(f"Creating GenericBox specialized for {item}")
        return f"GenericBox[{item.__name__}]"

print(GenericBox[int])  # GenericBox[int]
print(GenericBox[str])  # GenericBox[str]

此示例展示了 __class_getitem__ 如何拦截方括号表示法。当调用 GenericBox[int] 时,它会调用 __class_getitem__,并将 int 作为 item。

该方法在此处返回一个字符串以进行演示,但在实际使用中,它通常会返回该类的专用版本或 typing._GenericAlias。

使用 __class_getitem__ 进行类型提示

__class_getitem__ 主要用于支持 Python 中的类型提示。 以下是如何为自定义容器类型实现它。

type_hinting.py
from typing import Any, TypeVar, Generic

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, content: T):
        self.content = content

    @classmethod
    def __class_getitem__(cls, item):
        return super().__class_getitem__(item)

    def __repr__(self):
        return f"Box({self.content!r})"

# Type hints work with the custom container
def process_box(box: Box[int]) -> int:
    return box.content * 2

box = Box(42)
print(process_box(box))

此示例显示了一个通用的 Box 类,可以使用类型进行参数化。 __class_getitem__ 实现委托给父 Generic 类来正确处理类型参数化。

类型检查器会将 Box[int] 理解为包含整数的 box,而在运行时它会返回一个 typing._GenericAlias 实例。

自定义泛型类

您可以通过实现 __class_getitem__ 来创建自己的泛型类,而无需从 typing.Generic 继承。

custom_generic.py
class Pair:
    def __init__(self, first, second):
        self.first = first
        self.second = second

    def __class_getitem__(cls, items):
        if not isinstance(items, tuple):
            items = (items,)
        if len(items) != 2:
            raise TypeError("Pair requires exactly two type arguments")
        
        first_type, second_type = items
        return type(f"Pair[{first_type.__name__}, {second_type.__name__}]",
                   (cls,),
                   {'__annotations__': {'first': first_type, 'second': second_type}})

    def __repr__(self):
        return f"Pair({self.first!r}, {self.second!r})"

IntStrPair = Pair[int, str]
pair = IntStrPair(42, "answer")
print(pair)  # Pair(42, 'answer')

此自定义 Pair 类实现了自己的泛型类型支持。 __class_getitem__ 方法创建一个新的子类,该子类具有基于提供的类型参数的类型注解。

在运行时,Pair[int, str] 创建一个具有适当类型注解的新类,类型检查器可以使用该类进行静态类型检查。

运行时类型检查

虽然 __class_getitem__ 主要用于类型提示,但您可以将其与运行时类型检查结合使用,以获得更健壮的代码。

runtime_checking.py
class CheckedList:
    def __init__(self, items):
        self.items = list(items)
    
    def __class_getitem__(cls, item_type):
        class CheckedListSubclass(cls):
            def append(self, item):
                if not isinstance(item, item_type):
                    raise TypeError(f"Expected {item_type.__name__}, got {type(item).__name__}")
                super().append(item)
        return CheckedListSubclass
    
    def append(self, item):
        self.items.append(item)

IntList = CheckedList[int]
numbers = IntList([1, 2, 3])
numbers.append(4)
# numbers.append("four")  # Raises TypeError

此示例创建一个类型检查列表,该列表在运行时验证项目是否与指定的类型匹配。 __class_getitem__ 方法创建一个具有运行时类型检查的子类。

当调用 CheckedList[int] 时,它返回一个子类,该子类验证所有附加的项目都是整数。 这将静态类型提示与运行时验证相结合。

前向引用和字符串字面量

__class_getitem__ 可以使用字符串字面量处理前向引用,这对于引用尚未定义的类型的类型提示非常有用。

forward_refs.py
class Node:
    def __class_getitem__(cls, item):
        if isinstance(item, str):
            # Handle forward references
            return f"Node['{item}']"
        return f"Node[{item.__name__}]"

class Tree:
    pass

# Using forward reference
print(Node["Tree"])  # Node['Tree']
# Using actual type
print(Node[Tree])    # Node[Tree]

此示例演示了 __class_getitem__ 如何处理实际类型和字符串字面量。 字符串字面量用于类型提示中的前向引用。

类型检查器将识别此模式并正确处理类型注解中的前向引用,而在运行时它只会返回一个格式化的字符串。

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满热情的程序员,拥有丰富的编程经验。 自 2007 年以来,我一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我在编程教学方面拥有超过十年的经验。

列出所有 Python 教程