PEP 718 – 可下标的函数
- 作者:
- James Hilton-Balfe <gobot1234yt at gmail.com>
- 发起人:
- Guido van Rossum <guido at python.org>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 类型标注
- 创建日期:
- 2023年6月23日
- Python 版本:
- 3.15
- 发布历史:
- 2023年6月24日
摘要
本 PEP 提议使函数对象可用于类型标注的下标操作。这样做可以使开发者明确控制类型检查器生成的类型,尤其是在双向推断(允许推断匿名函数的参数类型)和其他非特殊化方法不足的情况下。它也使函数在可下标性方面与常规类保持一致。
动机
未知类型
目前,在某些情况下无法推断泛型函数的类型参数。
def make_list[T](*args: T) -> list[T]: ...
reveal_type(make_list()) # type checker cannot infer a meaningful type for T
使 FunctionType 的实例可下标将允许此构造函数被类型化。
reveal_type(make_list[int]()) # type is list[int]
目前,你必须使用赋值来提供精确的类型。
x: list[int] = make_list()
reveal_type(x) # type is list[int]
但这段代码对于一个简单的函数调用来说不必要地冗长,占用了多行。
类似地,此示例中的 T 目前无法有意义地推断,因此在没有额外赋值的情况下 x 是无类型的。
def factory[T](func: Callable[[T], Any]) -> Foo[T]: ...
reveal_type(factory(lambda x: "Hello World" * x))
然而,如果函数对象是可下标的,则可以给定更具体的类型。
reveal_type(factory[int](lambda x: "Hello World" * x)) # type is Foo[int]
不可判定的类型推断
甚至在子类关系导致类型推断不可能的情况下。但是,如果你可以专门化函数,类型检查器就可以推断出有意义的类型。
def foo[T](x: Sequence[T] | T) -> list[T]: ...
reveal_type(foo[bytes](b"hello"))
目前,类型检查器在这里不能始终一致地合成类型。
不可解的类型参数
目前,对于非专门化的字面量,无法确定类似以下情况的类型:
def foo[T](x: list[T]) -> T: ...
reveal_type(foo([])) # type checker cannot infer T (yet again)
reveal_type(foo[int]([])) # type is int
能够预先指定必须将特定类型传递给函数的情况也很有用。
words = ["hello", "world"]
foo[int](words) # Invalid: list[str] is incompatible with list[int]
允许下标操作使函数和方法与泛型类保持一致,而之前它们并不一致。虽然所有提议的更改都可以通过可调用的泛型类实现,但语法糖将非常受欢迎。
因此,专门化函数并将其用作新工厂是可行的。
make_int_list = make_list[int]
reveal_type(make_int_list()) # type is list[int]
单态化和具体化
这将实现一个据称已被多次请求的功能。
请注意,此功能并非由本 PEP 提出,但将来可能会实现。
此类功能的语法可能类似于:
def foo[T]():
return T.__value__
assert foo[int]() is int
基本原理
本 PEP 中的函数对象指的是 FunctionType、MethodType、BuiltinFunctionType、BuiltinMethodType 和 MethodWrapperType。
对于 MethodType,你应该能够这样写:
class Foo:
def make_list[T](self, *args: T) -> list[T]: ...
Foo().make_list[int]()
并使其与 FunctionType 类似地工作。
对于 BuiltinFunctionType,泛型内置函数(例如 max 和 min)将像 Python 中定义的函数一样工作。内置函数应尽可能像 Python 中实现的函数一样。
BuiltinMethodType 与 BuiltinFunctionType 类型相同。
MethodWrapperType(例如 object().__str__ 的类型)对于泛型魔术方法很有用。
规范
函数对象应该实现 __getitem__ 以允许在运行时进行下标操作,并返回一个 types.GenericAlias 实例,其中 __origin__ 设置为可调用对象,__args__ 设置为传递的类型。
类型检查器应支持函数下标,并理解传递给函数下标的参数应遵循与泛型可调用类相同的规则。
设置 __orig_class__
目前,__orig_class__ 是在 GenericAlias.__call__ 中设置的一个属性,指向创建被调用类的 GenericAlias 实例,例如:
class Foo[T]: ...
assert Foo[int]().__orig_class__ == Foo[int]
目前,__orig_class__ 是无条件设置的;但是,为了避免在任何创建的实例上可能发生擦除,如果 __origin__ 是任何函数对象的实例,则不应设置此属性。
如果没有此更改,以下代码片段在运行时将失败,因为 __orig_class__ 将是 bar[str] 而不是 Foo[int]。
def bar[U]():
return Foo[int]()
assert bar[str]().__orig_class__ == Foo[int]
与 @typing.overload 的交互
重载函数应该像以前一样工作,因为它们对运行时类型没有影响。唯一的改变是更多情况将变得可判定,并且行为/重载可以由开发者指定,而不是留给重载/联合的顺序决定。
向后兼容性
目前这些类不可子类化,因此对于已经实现 __getitem__ 的类,没有向后兼容性问题。
参考实现
所提议的运行时更改可以在此处找到 https://github.com/Gobot1234/cpython/tree/function-subscript
致谢
感谢 Alex Waygood 和 Jelle Zijlstra 对本 PEP 的反馈,以及 Guido 提供的一些启发性示例。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0718.rst
最后修改: 2025-05-06 20:54:28 GMT