python 類型注解


python 類型提示

本文參考自:https://www.cnblogs.com/poloyy/p/15170297.html

寫在前面:Python 運行時不強制執行函數和變量類型注解,但這些注解可用於類型檢查器、IDE、靜態檢查器等第三方工具。(換句話說,本文下面所說的類型,都是供代碼檢查器,IDE提示用的,並不是說你設置了類型,就真的變成像 java 那樣的強類型定義語言了)

簡單入門

python 是一門動態語言,因此在你拿到一個變量之前,你無法知道一個變量到底是什么類型的,因此當你傳遞一個變量給一個函數,盡管你內心知道這個參數會是某個特定的類型,但是 IDE 不知道它會是什么類型,因此它不會給你任何的代碼提示。

因此,Python 3.5、3.6 新增了兩個特性 PEP 484 和 PEP 526,幫助 IDE 為我們提供更智能的提示(並不會影響語言本身),簡單的寫法:

# 聲明變量時使用
num: int = 1
str1: str
    

# 函數參數也可以使用
def test(num: int = 1):
    return num

在定義一個變量時,直接在后面 : 類型 就可以給這個變量指定一個類型,后面可以繼續寫賦值語句 = 1,不寫賦值語句也不會出錯

指定函數返回值類型

def test(num: int = 1) -> int:  # -> 可以指明函數的返回值類型
    return num


def test(num: int = 1) -> None:  # -> 可以指明函數的返回值類型
    return None

在函數的冒號之前, -> 可以指定函數的返回值類型
注意,None 是一種類型提示特例

查看函數注解

我們可以通過 __annotations__ 查看一個函數的注解

def test(a: str, b: int = 2) -> None:
    pass

print(test.__annotations__)

打印結果: {'a': <class 'str'>, 'b': <class 'int'>, 'return': None}

常用類型

  • int, long, float: 整型,長整形,浮點型;

  • bool, str: 布爾型,字符串類型;

  • list, tuple, dict, set: 列表,元組,字典, 集合;

  • Iterable,Iterator: 可迭代類型,迭代器類型;

  • Generator: 生成器類型;

python3.9以后可以直接使用 list, dict, set, tuple 等類型,3.9 以前可能需要 from typing import List, Dict, Set, Tuple

py3.9 需要從 from collections.abc import Iterable, Generator, Iterator,3.9以前需要:from typing import Iterable, Generator, Iterator

list,set

list,set 類型提示,只可以給它們的內部元素指定一種類型,否則會報錯:

a: list[int] = []  # list[類型] ,list內部只能指定一種類型

a.append(1)
a.append('2')  # 盡管指定了 int 類型,但是可以添加 str 類型的元素。

# ---------------  同理: set[] ----------------------------

b: set[int] = {1}

我們之前說過,類型提示只是進行提示用的,並不會真的校驗我們給它的賦值。

list,set后面跟的是中括號: []

tuple

tuple 可以指定多種類型。

c: tuple[int, str] = (1, '2')  # 元組的第一個元素是 int 類型,第二個元素是 str 類型

如果想給 tuple 指定一種類型,但是你 tuple 的長度大於1,需要加上 ...

c: tuple[int, ...] = (1, 2, 3, 4)  # 代表無論元組有多少元素,都是 int 類型

dict

dict 可以指定兩種類型:分別代表鍵,值的類型:

d: dict[str, int] = {'1': 1}

嵌套類型

各個類型之間也可以嵌套:

e: dict[str, list[int]] = {'1': [1,2]}

類型的別名

其實類型的重命名,應該屬於python的用法,不是類型提示的特殊語法:

My_type = dict[str, list[int]]  # 給類型重命名
e:  My_type = {'1': [1,2]}

TypeAlias 別名

和上面別名一樣,只不過顯式的說明,這個一個別名。

from typing import TypeAlias

Factors: TypeAlias = list[int]  # 相當於 Factors = list[int]
    
x: Factors = [1,2,3]

自定義類型:NewType

和重命名有點類似,但是自定義類型相當於創建了某個類型的子類

UserId = NewType('UserId', int)  # 創建一個新類型(類型名字,類型)

num: UserId = UserId('1')
print(num, type(num))  # 1 <class 'str'>

可以看出,真實的數據類型還是 str , 說明了類型提示並不會對真實的代碼產生影響,只是IDE類型提示時會顯示該填入某種類型。

Literal

字面量類型,只接受指定的內容。

from typing import Literal

M = Literal['r','w','rb']  # 用戶輸入別的內容,會進行代碼提示

def test(mode: M):
    pass

test('xb')  # Expected type 'Literal['r', 'w', 'rb']', got 'Literal['xb']' instead 

Optional

這個類型代表的是當前參數是一個可選參數,只能接受一種類型,Optional[X] 等同於 X | None (或者Union[X, None]),也就說是,它默認包含了 None:

from typing import Optional

def test(x: Optional[int]=1):
    return x

Callable

Callable[[Arg1Type, Arg2Type], ReturnType] 可以用來提示一個可調用函數:

from typing import Callable

def test(a: int, b: int) -> int:  # 第一個函數,接受兩個 int 參數,返回 int
    return a+b

def test2(func: Callable[[int, int], int]):  # 第二個函數,類型是可調用對象:這個可調用對象的參數是 2 個int, 返回值是一個 int
    return func(1, 2)

test2(test)

Callable 接受兩個值,第一個值是包含可調用對象的所有參數類型的列表。第二個值是可調用對象的返回值類型。

如果調用對象沒有參數,或者有放多參數,可以這樣寫:

Callable[[], int] 無參數,返回 int

Callable[..., int] 不限定參數,返回 int

ClassVar 類變量

聲明類變量的類型

class Starship:
    stats: ClassVar[dict[str, int]] = {} # class variable
    damage: int = 10                     # instance variable

泛型 Generic

from typing import TypeVar, Generic

T = TypeVar('T')  # "T" 代表任意類型


# A 繼承自 Generic[T] ,所以這個類帶有類型
class A(Generic[T]):
    def __init__(self, value: T):  # 初始化值:T 類型
        self.value = value

    def get_value(self) -> T:  # 返回 T 類型
        return self.value

    def set_value(self, value: T):  # 設置的值:T 類型
        self.value = value


def test(a: A[int]):  # 注意:test 函數接受的參數,需是 A[int] 類型
    print(a.value)
    a.set_value('str1')  # 這里會提示:Expected type 'int' (matched generic type 'T'), got 'str' instead
    print(a.get_value())


a = A[str]('str1')  # 實例化一個 str 類型的 A 對象
# b = A[int](2)  # 也可以實例化一個 int 類型的 A 對象
test(a)  # 將 str 類型的 A 對象,傳遞給 test,但是注意:test 接受的類型是 A[int]

上面的例子中,A 類繼承了 Generic[T] ,代表了 A 類是一個有類型的類。我們在實例化它時,可以隨意指定它的類型:A[int](2) ,但是一旦我們指定了某個類型,這個類中所有的 T類型 都會和實例化時的類型一致:int

因此在 test(a: A[int]) 函數中,我們指定了要接受 A[int] 類型的參數。而我們傳遞的卻是 A[str]("str1") 對象。所以調用 a.set_value("str1") 時,IDE 會提示類型不正確(但是代碼能跑通,還是之前說的,它僅僅是做類型提示,不會真的影響賦值操作)

多類型

Any

任意類型

from typing import Any

a: Any = None
a = []          # OK
a = 2           # OK

TypeVar

TypeVar 可以指定多種類型,或者任意類型

任意類型

from typing import TypeVar, list

T = TypeVar("T")  # T 代表了任意類型

x: T = 2
y: T = 'abc'

指定類型

from typing import TypeVar, list


My = TypeVar('My', int, str, list[int])  # 自定義一個類型,可以是 int 或 str 或 list[int] 之一

xx: My = 'ss'
yy: My = [1]

注意這里的 TypeVar() ,后面用的是 () ,不是 []

Union

Union 也可以指定多種類型。

from typing import TypeVar, list, Union

x:Union[int, str] = '1'  # 可以是 int 或 str 類型

Union 是不分順序的,並且如果有多個重復的類型,會自動去重:

print(
    Union[int, str, int, Union[int, list[int]]] == Union[list[int], int, str]
)

# True

這個例子中,重復的 int 會自動去重,並且里面類型的順序也不重要。等式前面的Union中還嵌套了一個 Union[int, list[int]] ,也會被提取出來

Python 3.10 版本添加了 | 可以用來簡化 Union 的操作:

x: int | str  # 等同於 x: Union[int, str]

| 也可以用於 isinstance, issubclass 等函數中:

isinstance(1, int | str)  # True

@overload

有一些函數經常使用 @overload 裝飾器,這其實也是類型提示的一種:

from typing import overload


@overload
def test(a: int):  # 僅用來進行參數的類型檢查,說明它支持 int 類型,不用實現函數(即不寫函數內容)
	...


@overload
def test(a: str):  # 僅用來類型檢查,說明支持 str
	...


def test(a):  # 很重要,必須寫一個不使用 @overload 的正常的函數。這個函數才是真正被調用的函數。
	print(type(a))


test(2)

@overload 裝飾器可以修飾支持多個不同參數類型組合的函數或方法。后續必須緊跟一個非 @overload 裝飾定義的同樣的函數。@overload-裝飾定義僅是為了協助類型檢查器, 因為該裝飾器會被非 @overload-裝飾定義覆蓋

也就是說,你可以寫一堆同名函數,這些函數可以接收不同類型的參數,用 overload 來裝飾這些同名函數,並且最后一定要寫一個正常的、不用overload 裝飾的同名函數。這樣代碼提示就會知道這個函數可以接收哪些類型的參數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM