如何更好的使用 Python 的類型提示?
使用動態(tài)語言一時爽,代碼重構(gòu)火葬場。相信你一定聽過這句話,和單元測試一樣,雖然寫代碼的時候花費你少量的時間,但是從長遠來看,這是非常值得的。本文分享如何更好的理解和使用 Python 的類型提示。
1、類型提示僅在語法層面有效
類型提示(自 PEP 3107 開始引入)用于向變量、參數(shù)、函數(shù)參數(shù)以及它們的返回值、類屬性和方法添加類型。
Python 的變量類型是動態(tài)的,可以在運行時修改,為代碼添加類型提示,僅在語法層面支持,對代碼的運行沒有任何影響,Python 解釋器在運行代碼的時候會忽略類型提示。
因此類型提示一個直觀的作用就是提升代碼的可讀性,方便調(diào)用者傳入/傳出恰當類型的參數(shù),便于代碼重構(gòu)。
Python 內(nèi)置的基本類型可以直接用于類型提示:
變量的類型提示示例:
a: int = 3
b: float = 2.4
c: bool = True
d: list = ["A", "B", "C"]
e: dict = {"x": "y"}
f: set = {"a", "b", "c"}
g: tuple = ("name", "age", "job")
函數(shù)的類型提示:
def add_numbers(x: type_x, y: type_y, z: type_z= 100) -> type_return:
return x + y + z
這里的 type_x , type_y , type_z , type_return 可以是內(nèi)置的基本類型,也可以是自定義類型。
類的類型提示:
class Person:
first_name: str = "John"
last_name: str = "Does"
age: int = 31
2、用 mypy 檢查類型提示
假如有這樣一段代碼:
x: int = 2
x = 3.5
用 Python 解釋器執(zhí)行是不會有任何錯誤的:
借助于 mypy 就可以,先 pip install mypy 安裝一下,然后 mypy script.py 即可:
更多 mypy 相關(guān)可以參考前文mypy 這個工具,讓Python的類型提示變得非常實用。
3、類型提示的好處
如果解釋器沒有強制執(zhí)行類型提示,為什么還要編寫類型提示呢?確實,類型提示不會改變代碼的運行方式:Python 本質(zhì)上是動態(tài)類型的,這一點不太可能會改變。但是,從開發(fā)人員經(jīng)驗的角度來看,類型提示有很多好處。
(1)、使用類型提示,尤其是在函數(shù)中,通過類型提示來明確參數(shù)類型和所產(chǎn)生結(jié)果的類型,非常便于閱讀和理解。
(2)、類型提示消除了認知開銷,并使代碼更易于閱讀和調(diào)試??紤]到輸入和輸出的類型,你可以輕松推斷對象以及它們?nèi)绾握{(diào)用。
(3)、類型提示可改善代碼編輯體驗。IDE 可以依靠類型檢測來靜態(tài)分析你的代碼并幫助檢測潛在的錯誤(例如,傳遞錯誤類型的參數(shù)、調(diào)用錯誤的方法等)。另外,還可以根據(jù)類型提示為每個變量提供自動補全。
IDE 的類型檢查
IDE 的類型檢查
IDE 類型檢查后的自動補全
4、List 用法
假如你需要列表 list 內(nèi)部是 float 的類型提示,這樣做是不行的:
def my_dummy_function(l: list[float]):
return sum(l)
標準庫 typing 考慮到了這個問題,你可以這樣:
from typing import List
def my_dummy_function(vector: List[float]):
return sum(vector)
5、Dict 用法
假如要提示這樣的類型:
my_dict = {"name": "Somenzz", "job": "engineer"}
借助于 Dict,你可以這樣定義類型:
from typing import Dict
my_dict_type = Dict[str, str]
my_dict: my_dict_type = {"name": "Somenzz", "job": "engineer"}
6、TypedDict 用法
假如你需要提示這樣的類型,那該怎么辦?
d = {"name": "Somenzz", "interests": ["chess", "tennis"]}
借助于 TypedDict ,你可以這樣:
TypedDict
7、Union 用法
從 Python 3.10 開始,Union 被替換為 | 這意味著 Union[X, Y] 現(xiàn)在等價于 X | Y。
Union[X, Y](或 X | Y)表示 X 或 Y。
假設(shè)你的函數(shù)需要從緩存目錄中讀取文件并加載 Torch 模型。此緩存目錄位置可以是字符串值(例如 /home/cache ),也可以是 Pathlib 庫的 Path 對象,在這種情況下,代碼如下:
def load_model(filename: str, cache_folder: Union[str, Path]):
if isinstance(cache_folder, Path):
cache_folder = str(cache_folder)
model_path = os.join(filename, cache_folder)
model = torch.load(model_path)
return model
8、Callable 用法
當你需要傳入一個函數(shù)作為參數(shù)的時候,這個參數(shù)的類型提示可以為 Callable。
from typing import Callable
def sum_numbers(x: int, y: int) -> int:
return x + y
def foo(x: int, y: int, func: Callable) -> int:
output = func(x, y)
return output
foo(1, 2, sum_numbers)
你還可以給這樣的函數(shù)參數(shù)指定參數(shù)列表,真的很強大:
語法:
Callable[[input_type_1, ...], return_type]
示例:
def foo(x: int, y: int, func: Callable[[int, int], int]) -> int:
output = func(x, y)
return output
9、Any 用法
當你傳入的參數(shù)可以為任何類型的時候,就可以使用 Any
def bar(input: Any):
...
10、Optional 用法
如果你的函數(shù)使用可選參數(shù),具有默認值,那么你可以使用類型模塊中的 Optional 類型。
from typing import Optional
def foo(format_layout: Optional[bool] = True):
...
11、Sequence 用法
Sequence 類型的對象是可以被索引的任何東西:列表、元組、字符串、對象列表、元組列表的元組等。
from typing import Sequence
def print_sequence_elements(sequence: Sequence[str]):
for i, s in enumerate(s):
print(f"item {i}: {s}"
12、Tuple 用法
Tuple 類型的工作方式與 List 類型略有不同,Tuple 需要指定每一個位置的類型:
from typing import Tuple
t: Tuple[int, int, int] = (1, 2, 3)
如果你不關(guān)心元組中每個元素的類型,你可以繼續(xù)使用內(nèi)置類型 tuple。
t: tuple = (1, 2, 3, ["cat", "dog"], {"name": "John"})
最后的話
類型提示在代碼之上帶來了額外的抽象層:它們有助于記錄代碼,澄清關(guān)于輸入/輸出的假設(shè),并防止在頂部執(zhí)行靜態(tài)代碼分析 (mypy) 時出現(xiàn)的隱蔽和錯誤。