Following system colour scheme - Python 增強提案 Selected dark colour scheme - Python 增強提案 Selected light colour scheme - Python 增強提案

Python 增強提案

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 中的函式物件指的是 FunctionTypeMethodTypeBuiltinFunctionTypeBuiltinMethodTypeMethodWrapperType

對於 MethodType,你應該能夠這樣寫:

class Foo:
    def make_list[T](self, *args: T) -> list[T]: ...

Foo().make_list[int]()

並使其與 FunctionType 類似地工作。

對於 BuiltinFunctionType,泛型內建函式(例如 maxmin)將像 Python 中定義的函式一樣工作。內建函式應儘可能像 Python 中實現的函式一樣。

BuiltinMethodTypeBuiltinFunctionType 型別相同。

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 提供的一些啟發性示例。


來源: https://github.com/python/peps/blob/main/peps/pep-0718.rst

最後修改: 2025-05-06 20:54:28 GMT