Python typing 模組 – 有效地使用類型檢查器

自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 原本是一種動態類型的語言!


參考資料


Source:
https://www.digitalocean.com/community/tutorials/python-typing-module