typing --- 支援型別提示¶
在 3.5 版被加入.
原始碼:Lib/typing.py
備註
Python runtime 不強制要求函式與變數的型別註釋。他們可以被第三方工具使用,如:型別檢查器、IDE、linter 等。
此模組提供 runtime 型別提示支援。
動腦筋思考下面的函式:
def surface_area_of_cube(edge_length: float) -> str:
return f"The surface area of the cube is {6 * edge_length ** 2}."
函式 surface_area_of_cube 需要一個引數且預期是一個 float 的實例,如 edge_length: float 所指出的型別提示。這個函式預期會回傳一個 str 的實例,如 -> str 所指出的提示。
儘管型別提示可以是簡單類別,像是 float 或 str,他們也可以變得更為複雜。模組 typing 提供一組更高階的型別提示詞彙。
新功能會頻繁的新增至 typing 模組中。typing_extensions 套件為這些新功能提供了 backport(向後移植的)版本,提供給舊版本的 Python 使用。
也參考
- 型別小抄 (Typing cheat sheet)
型別提示的快速預覽(發布於 mypy 的文件中)
- mypy 文件的 型別系統參考資料 (Type System Reference) 章節
Python 的加註型別系統是基於 PEPs 進行標準化,所以這個參照 (reference) 應該在多數 Python 型別檢查器中廣為使用。(某些部分依然是特定給 mypy 使用。)
- Python 的靜態型別 (Static Typing)
由社群編寫的跨平台型別檢查器文件 (type-checker-agnostic) 詳細描述加註型別系統的功能、實用的加註型別衍伸工具、以及加註型別的最佳實踐 (best practice)。
Python 型別系統的技術規範¶
關於 Python 型別系統標準的 (canonical)、最新的技術規範可以在Python 型別系統的技術規範找到。
型別別名¶
一個型別別名被定義來使用 type 陳述式,其建立了 TypeAliasType 的實例。在這個範例中,Vector 及 list[float] 會被當作和靜態型別檢查器一樣同等對待:
type Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# passes type checking; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
型別別名對於簡化複雜的型別簽名 (complex type signature) 非常好用。舉例來說:
from collections.abc import Sequence
type ConnectionOptions = dict[str, str]
type Address = tuple[str, int]
type Server = tuple[Address, ConnectionOptions]
def broadcast_message(message: str, servers: Sequence[Server]) -> None:
...
# The static type checker will treat the previous type signature as
# being exactly equivalent to this one.
def broadcast_message(
message: str,
servers: Sequence[tuple[tuple[str, int], dict[str, str]]]
) -> None:
...
type 陳述式是 Python 3.12 的新功能。為了向後相容性,型別別名可以透過簡單的賦值來建立:
Vector = list[float]
或是用 TypeAlias 標記,讓它明確的表示這是一個型別別名,而非一般的變數賦值:
from typing import TypeAlias
Vector: TypeAlias = list[float]
NewType¶
使用 NewType 輔助工具 (helper) 建立獨特型別:
from typing import NewType
UserId = NewType('UserId', int)
some_id = UserId(524313)
若它是原本型別的子類別,靜態型別檢查器會將其視為一個新的型別。這對於幫助擷取邏輯性錯誤非常有用:
def get_user_name(user_id: UserId) -> str:
...
# passes type checking
user_a = get_user_name(UserId(42351))
# fails type checking; an int is not a UserId
user_b = get_user_name(-1)
你依然可以在對於型別 UserId 的變數中執行所有 int 的操作。這讓你可以在預期接受 int 的地方傳遞一個 UserId,還能預防你意外使用無效的方法建立一個 UserId:
# 'output' is of type 'int', not 'UserId'
output = UserId(23413) + UserId(54341)
注意這只會透過靜態型別檢查器強制檢查。在 runtime 中,陳述式 (statement) Derived = NewType('Derived', Base) 會使 Derived 成為一個 callable(可呼叫物件),會立即回傳任何你傳遞的引數。這意味著 expression (運算式)Derived(some_value) 不會建立一個新的類別或過度引入原有的函式呼叫。
更精確地說,expression some_value is Derived(some_value) 在 runtime 永遠為 true。
這會無法建立一個 Derived 的子型別:
from typing import NewType
UserId = NewType('UserId', int)
# Fails at runtime and does not pass type checking
class AdminUserId(UserId): pass
無論如何,這有辦法基於 '衍生的' NewType 建立一個 NewType:
from typing import NewType
UserId = NewType('UserId', int)
ProUserId = NewType('ProUserId', UserId)
以及針對 ProUserId 的型別檢查會如期運作。
更多細節請見 PEP 484。
備註
請記得使用型別別名是宣告兩種型別是互相相等的。使用 type Alias = Original 則會讓靜態型別檢查器在任何情況之下將 Alias 視為與 Original 完全相等。這當你想把複雜的型別簽名進行簡化時,非常好用。
相反的,NewType 宣告一個型別會是另外一種型別的子類別。使用 Derived = NewType('Derived', Original) 會使靜態型別檢查器將 Derived 視為 Original 的子類別,也意味著一個型別為 Original 的值,不能被使用在任何預期接收到型別 Derived 的值的區域。這當你想用最小的 runtime 成本預防邏輯性錯誤而言,非常有用。
在 3.5.2 版被加入.
在 3.10 版的變更: 現在的 NewType 比起一個函式更像一個類別。因此,比起一般的函式,呼叫 NewType 需要額外的 runtime 成本。
在 3.11 版的變更: 呼叫 NewType 的效能已經恢復與 Python 3.9 相同的水準。
註釋 callable 物件¶
函式,或者是其他 callable 物件,可以使用 collections.abc.Callable 或以棄用的 typing.Callable 進行註釋。 Callable[[int], str] 象徵為一個函式,可以接受一個型別為 int 的引數,並回傳一個 str。
舉例來說:
from collections.abc import Callable, Awaitable
def feeder(get_next_item: Callable[[], str]) -> None:
... # 主體
def async_query(on_success: Callable[[int], None],
on_error: Callable[[int, Exception], None