Python Type Hint 型別註解教學 - 基礎篇
型別註釋功能可以讓身為動態語言的 Python 無痛的帶來了靜態語言的優點,同時又保持原本動態語言的靈活性,如果看不懂,講人話就是
- 提高可讀性:開發者可以清楚知道函數或變數預期的資料型別,不需要額外翻閱文件或原始碼。
- 減少錯誤:型別提示可以搭配 mypy 等工具檢查型別不同的錯誤。
- 工具整合:現代 IDE 可以利用型別提示提供自動完成和建議。
初階使用網路上已經有非常多文章就不重複撰寫,附上筆者整理後覺得最好的資源,講的非常好:
- 【python】Type Hint入门与初探,好好的python写什么类型标注?
- 【python】Type Hint的进阶知识,这下总该有你没学过的内容了吧?
- Python 类型体操训练(一)-- 基础篇
- Python 类型体操训练(二)-- 中级篇
- Python 类型体操训练(三)-- 高级篇
建議看幾個資源就好,筆者發現大部分的中文文章品質很差不建議閱讀。
注意事項
覺得注意事項的內容還算有用而且網路上比較少有人提到,於是決定搬到最前面。
- Union 能少用就少用,因為 IDE 和檢查系統會一直跟你說後續型別不符,除非有穩健的 type narrowing,不然我覺得用 Union 不如直接寫 Any 就好了。
- 有時候遇到一個奇怪的變數不知道是什麼型別,mypy 提供
reveal_type
和reveal_locals
兩種方法偵錯,使用時不需 import 直接用,在終端機執行 mypy example.py 即可,詳情請見這篇文章。 - Type hint 不是越多越好,靈活迭代、快速開發才是 Python 的專長,過度的 type hint 反而浪費時間,尤其在他的資源相對稀缺,而且寫出一個很複雜的 type hint 對於開發沒有實質效率幫助的情況下。
- List, Dict 這些從 typing import 的型別註解在 Python 3.9 之後就已經內建,而 3.8 已經 EOL (end of life, 產品壽命結束),所以除非需要兼容過往系統否則完全沒有必要使用大寫版本的。
- Python 3.10 之後預設啟用
from __future__ import annotations
。
初階使用
只記錄基礎關鍵字,方便讀者快速查找,基本上只用這些關鍵字也能完成九成以上的 type hint。
- list/dict/tuple/set: 列表/集合/元組/字典
- Union: 接受 Union 中的所有類型,可直接用
list | dict | tuple
替代 - Optional: 接受 Optional 中的所有類型或者 None,可直接用
list | None
替代 - Literal: 限制只能使用指定輸入,通常用於常數
- Callable: 可以呼叫的對象,使用方式是
Callable[[input_type1, input_type2], output_type]
- Iterable: 可以迭代的對象(該對象存在
__iter__
方法,例如 list) - Final: 最終結果,不應該被覆寫
if TYPE_CHECKING
: 只有在型別檢查時才會啟用此區塊 ,通常用於啟用型別提示系統,避免真的 import
接下來是稍微複雜一點的型別註解。
NoReturn
NoReturn 告訴型別註解系統這個函式應該要出錯 (raise exception),連 None 都不會返回。
NoReturn: 【python】Type Hint入门与初探,好好的python写什么类型标注?@198s
NewType/TypeAlias
- NewType: 新增一個型別,如新增
UserId = NewType('UserId', int)
此類別會和int
型別不同。 - TypeAlias: 建立別名,和 NewType 的差異是前者用於建立別名,後者用於新建一個「不同的」類型。建立別名的目的僅是方便記憶和開發管理。
from typing import NewType, TypeAlias
# 分別定義 NewType, TypeAlias 作為範例
UserId = NewType('UserId', int)
Age: TypeAlias = int
def get_user_age(user_id: UserId, age: Age) -> str:
return f"User {user_id} is {age} years old" # 正確
# 正確
user_id = UserId(1234) # 正確
age: Age = 25 # 正確
print(get_user_age(user_id, age)) # 正確
# 錯誤
user_id_wrong = 1234 # 沒有使用 UserId
age_wrong: Age = "25" # 錯誤:Age 應為 int
print(get_user_age(user_id_wrong, age_wrong)) # 錯誤:int 和 UserId 是不同類型變數
NewType: 【python】Type Hint入门与初探,好好的python写什么类型标注?@418s
ClassVar
告訴 Python 這是類型變數而不是實例變數,不能在實例化後修改
類別變數是所有實例共享的,實例變數則是每個實例獨立擁有的
from typing import ClassVar
class MyClass:
CLS_VAR: ClassVar[str] = "My Class"
a = MyClass
a.CLS_VAR = "newvalue" # 正確
b = MyClass()
b.CLS_VAR = "newvalue" # 錯誤
# 使用字典要小心
class MyClass:
URL_MAPPINGS: ClassVar[dict[str, str]] = {
"album": "scrape_album",
"actor": "scrape_actor",
}
a = MyClass
a.URL_MAPPINGS["new key"] = "newvalue" # 正確
b = MyClass()
b.URL_MAPPINGS["new key"] = "newvalue" # 正確
b.URL_MAPPINGS = {"new album": "value"} # 錯誤
高階使用
可以跳過這裡也沒關係,完全不用這裡的 type hint 也不太會對型別註解系統帶來問題或是降低功能。
overload/override
用於提示 mypy 輸入輸出型別的多載的裝飾器,和 C++ 真正意義上的多載不同,只用於提示 mypy/IDE 而已。overload 用於函式或方法之間,override 用於繼承之間。
寫一寫會不小心忘記這些只是提示,就像這篇文章一樣,請記得 type hint 完全不影響 Python 實際運作。
Protocol
檢查該類別是否都實作相同的方法,軟性限制需要實作相同方法,和抽象方法 (abstractmethod) 的差異是抽象方法是硬性限制,前者還是可以執行(畢竟只是 hint,後者無法執行)。
符合 Python 鴨子型別的 typing,長得像就好,其他隨便你
from typing import Protocol
# 定義一個 Protocol,指定必須有 `speak` 方法
class Speaker(Protocol):
def speak(self) -> str: ...
class Dog:
def speak(self) -> str:
return "Woof!"
class Robot:
def not_speak(self) -> str:
return "Beep!"
# 指定輸入符合 Speaker 這種類型的 Protocol
def test(entity: Speaker) -> None:
pass
dog = Dog()
robot = Robot()
print(test(dog)) # 正確
print(test(robot)) # 錯誤
TypedDict
用於限制字典的 key-value pair 的變數類型,就是軟限制版本的 dataclass,個人覺得滿難用的,除非 dict 資料型別絕對不會變動才使用他。
不常用關鍵字
- Annotated: 用於附註變數。
- Self: 回傳類別本身。
- typeguard: 用於 type narrowing。
版本紀錄
紀錄 Python 各個版本新增的 type hint 功能方便快速查找
- 3.8: 新增
Protocol
- 3.9: 內建
list/set/tuple/dict
,不再需要從 typing 載入,還有很多 collections 不再建議從 typing 載入 - 3.9: 新增
Annotated
功能 - 3.10:
Union
關鍵字可以用管道符號 | 代替 - 3.10: 預設使用
from __future__ import annotations
,此功能允許延遲型別提示,允許定義類別時使用類別自身作為型別提示 - 3.12:
Generic
新增了語法class MyClass[T]
,舊版語法是class MyClass(Generic[T])
- 3.12:
TypeAlias
支援語法type Vector = list[float]
,舊版語法是Vector: TypeAlias = list[float]
- 3.12: 新增
Override
- 3.14: typing 中的
List/Set/Tuple/Dict
將被標記為 deprecated