PEP 718 – 可下标函数
- 作者:
- James Hilton-Balfe <gobot1234yt at gmail.com>
- 赞助:
- Guido van Rossum <guido at python.org>
- 讨论列表:
- Discourse 线程
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 类型提示
- 创建:
- 2023年6月23日
- Python 版本:
- 3.13
- 历史记录:
- 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