自Python 3.5開始,Python的typing模組試圖提供一種提示類型的方式,以幫助靜態類型檢查器和linters準確預測錯誤。
由於Python在運行時需要確定對象的類型,開發人員有時很難弄清楚代碼中到底發生了什麼。
即使是像PyCharm IDE這樣的外部類型檢查器也不能產生最佳結果;根據StackOverflow上的這個答案,平均只能正確預測錯誤約50%的時間。
Python通過引入所謂的類型提示(類型註解)來解決這個問題,以幫助外部類型檢查器識別任何錯誤。這是一種程序員在編譯時提示正在使用的對象的類型的好方法,並確保類型檢查器正確工作。
這使得Python代碼更易讀且更健壯,也更容易被其他讀者理解!
注意:這在編譯時不進行實際的類型檢查。如果實際返回的對象的類型與提示的類型不同,將不會有編譯錯誤。這就是為什麼我們使用外部類型檢查器(如mypy)來識別任何類型錯誤的原因。
推薦先備知識
為了有效使用typing
模塊,建議您使用外部類型檢查器/程式碼檢查工具進行靜態類型匹配檢查。在Python中,最廣泛使用的類型檢查器之一是mypy,因此我建議您在閱讀本文的其餘部分之前安裝它。
我們已經介紹了Python中的類型檢查的基礎知識。您可以先閱讀這篇文章。
在本文中,我們將使用mypy
作為靜態類型檢查器,可以通過以下方式安裝:
pip3 install mypy
您可以運行mypy
對任何Python文件進行類型匹配檢查。這就像您正在‘編譯’Python代碼一樣。
mypy program.py
在調試錯誤後,您可以使用以下方式正常運行程序:
python program.py
現在我們已經完成了先備知識,讓我們嘗試使用該模塊的一些功能。
類型提示 / 類型註釋
關於函數
我們可以用註解來指定函數的返回類型和參數的類型。
def print_list(a: list) -> None:
print(a)
這樣可以通知類型檢查器(在我這裡是作為參數並返回
def print_list(a: list) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
讓我們先在類型檢查器
vijay@JournalDev:~ $ mypy printlist.py
printlist.py:5: error: Argument 1 to "print_list" has incompatible type "int"; expected "List[Any]"
Found 1 error in 1 file (checked 1 source file)
正如預期的那樣,我們得到了一個錯誤,因為第5行的參數是一個。
關於變量
自從Python 3.6開始,我們還可以註解變量的類型,並提到該類型。但是如果你希望在函數返回之前變量的類型改變,這不是強制性的。
# 將'radius'註解為浮點數
radius: float = 1.5
# 我們可以註解一個變量而不賦值!
sample: int
# 將'area'註解為返回浮點數
def area(r: float) -> float:
return 3.1415 * r * r
print(area(radius))
# 使用'__annotations__'字典打印函數的所有註解
# mypy的輸出
print('Dictionary of Annotations for area():', area.__annotations__)
輸出結果:
vijay@JournalDev: ~ $ mypy find_area.py && python find_area.py
Success: no issues found in 1 source file
7.068375
Dictionary of Annotations for area(): {'r': <class 'float'>, 'return': <class 'float'>}
這是使用 mypy
的推薦方式,首先提供類型註釋,然後再使用類型檢查器。
類型別名
typing
模組為我們提供了類型別名,這是通過將類型賦值給別名來定義的。
from typing import List
# 向量是浮點值列表
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
a = scale(scalar=2.0, vector=[1.0, 2.0, 3.0])
print(a)
輸出
vijay@JournalDev: ~ $ mypy vector_scale.py && python vector_scale.py
Success: no issues found in 1 source file
[2.0, 4.0, 6.0]
在上面的片段中,Vector
是一個別名,代表浮點值列表。我們可以對別名進行類型提示,這就是上面的程式所做的。
讓我們再看一個例子,檢查字典中的每個鍵值對,檢查它們是否與姓名:電子郵件格式匹配。
from typing import Dict
import re
# 創建一個名為 'ContactDict' 的別名
ContactDict = Dict[str, str]
def check_if_valid(contacts: ContactDict) -> bool:
for name, email in contacts.items():
# 檢查姓名和電子郵件是否為字符串
if (not isinstance(name, str)) or (not isinstance(email, str)):
return False
# 檢查電子郵件 [email protected] 格式
if not re.match(r"[a-zA-Z0-9\._\+-]+@[a-zA-Z0-9\._-]+\.[a-zA-Z]+$", email):
return False
return True
print(check_if_valid({'vijay': '[email protected]'}))
print(check_if_valid({'vijay': '[email protected]', 123: '[email protected]'}))
mypy 的輸出
vijay@JournalDev:~ $ mypy validcontacts.py
validcontacts.py:19: error: Dict entry 1 has incompatible type "int": "str"; expected "str": "str"
Found 1 error in 1 file (checked 1 source file)
在這裡,由於我們第二個字典上的 name
參數是一個整數 (123),我們在mypy
中得到了一個靜態編譯時錯誤。因此,別名是從 mypy
強制進行準確類型檢查的另一種方式。
使用NewType()创建用户定义的数据类型
我们可以使用NewType()
函数创建新的用户定义类型。
from typing import NewType
# 创建一个名为'StudentID'的新用户类型
# 包含一个整数
StudentID = NewType('StudentID', int)
sample_id = StudentID(100)
静态类型检查器将把新类型视为原始类型的子类,这对于捕捉逻辑错误非常有用。
from typing import NewType
# 创建一个名为'StudentID'的新用户类型
StudentID = NewType('StudentID', int)
def get_student_name(stud_id: StudentID) -> str:
return str(input(f'Enter username for ID #{stud_id}:\n'))
stud_a = get_student_name(StudentID(100))
print(stud_a)
# 这是不正确的!!
stud_b = get_student_name(-1)
print(stud_b)
输出 来自mypy
vijay@JournalDev:~ $ mypy studentnames.py
studentnames.py:13: error: Argument 1 to "get_student_name" has incompatible type "int"; expected "StudentID"
Found 1 error in 1 file (checked 1 source file)
Any类型
这是一种特殊类型,通知静态类型检查器(在我这里是mypy
)每种类型都与此关键字兼容。
考虑我们的旧print_list()
函数,现在接受任何类型的参数。
from typing import Any
def print_list(a: Any) -> None:
print(a)
print_list([1, 2, 3])
print_list(1)
现在,当我们运行mypy
时,不会出现错误。
vijay@JournalDev:~ $ mypy printlist.py && python printlist.py
Success: no issues found in 1 source file
[1, 2, 3]
1
def foo(bar):
return bar
# 靜態類型檢查器將將上述
# 視為具有相同簽名:
def foo(bar: Any) -> Any:
return bar
因此,您可以使用 Any 混合靜態和動態類型的程式碼。
結論
在本文中,我們已經了解了 Python typing 模組,在類型檢查的情況下非常有用,允許像 mypy
這樣的外部類型檢查器準確報告任何錯誤。
這為我們提供了一種在 Python 中編寫靜態類型程式碼的方法,而 Python 原本是一種動態類型的語言!
參考資料
- Python typing 模組的官方文件(這包含了此模組中更多方法的詳細資訊,我建議將其作為次要參考資料)
- 關於型別提示的 StackOverflow 問題(這提供了關於該主題的非常好的討論。我強烈建議您也閱讀此主題!)
Source:
https://www.digitalocean.com/community/tutorials/python-typing-module