在Python語言發展的程序中,PEP提案發揮了巨大的作用,如PEP 3107 和 PEP 484提案,分別給我們帶來了函式注解(Function Annotations)和型別提示(Type Hints)的功能,
PEP 3107:定義了函式注解的語法,允許為函式的引數和回傳值添加元資料注解,
PEP 484:按照PEP 3107函式注解的語法,從Python語法層面全面支持型別提示,型別提示可以是內置型別、內置類、抽象基類、types模塊中提供的型別和開發人員自定義的類,
另外 PEP 526, PEP 544, PEP 586, PEP 589, PEP 591 這些東西對 PEP 3107 和 PEP 484 進行了補充,比如添加了變數注釋,字面量注釋這些東西,
需要注意的是,型別提示僅有提示的作用,這里的提示是指用戶閱讀Python代碼的時候的提示,僅在語法層面支持,對代碼的運行沒有任何影響,Python 解釋器在運行代碼的時候會忽略型別提示,也就是說,Python的型別提示僅是為了提升代碼可讀性,一定程度上緩解"動態語言一時爽,代碼重構火葬場"的尷尬,
下面將函式注解和型別提示,統稱為型別注解,
型別注解優點
1、可以使Python擁有部分靜態語言的特性,利用型別注解可以實作一種類似型別宣告的效果,提升代碼的可讀性及后續的可維護性,
2、型別注解可以讓IDE(如pycharm)像靜態語言那樣分析我們的代碼,及時給我們相應的提示,如下圖對比:


3、多多使用型別注解,不僅可以讓Python擁有強型別語言的嚴謹,還能保持Python作為動態型別語言的靈活性,
普通變數型別注解
在宣告變數時,變數的后面可以加一個冒號,后面再寫上變數的型別,如 int、list 等等,以此實作型別注解,
a: int = 22
b: str = "name"
c: float = 55.5
d: bool = True
e: list = [1, 2, 3]
f: set = {1, 2, 3}
g: dict = {"name": "ming", "age": 22}
h: tuple = (1, 2, 3)
i: bytes = b'world'
j: bytearray = bytearray("world")
函式引數及回傳值型別
函式引數的型別宣告就是冒號+型別即可,和普通變數型別宣告沒區別,
函式回傳值的型別宣告是用箭頭指向具體的型別,如果是回傳值有多個,使用元組包裹即可(因為函式的多個回傳值就是以元組形式回傳的),需要注意的是,箭頭左右兩邊都要留有空格,
def handler(a: int, b: int) -> int:
return a + b
def handler2(a: int, b: int, *args: int) -> int:
return a + b + sum(args)
def handler3(a: int, b: int, *args: int, **kwargs: int) -> (int, str):
return a + b + sum(args) + sum(kwargs.values()), ""
typing模塊
typing模塊的加入不會影響程式的運行,也不會報正式的錯誤,pycharm支持檢測基于typing注解的錯誤,不符合規定型別注解時會出現黃色警告,但不會影響程式運行,
容器型別 & 復合型別
串列、字典、元組等包含元素的復合型別,用簡單的 list,dict,tuple 不能夠明確說明內部元素的具體型別,
此外,Python本身就是動態型別的語言,如果我們強制使用某種型別,一定程度上會喪失Python作為動態語言的優勢,因此 typing 模塊提供了一種復合型別注解的語法,即一個引數即可以是型別A,也可以是型別B或者型別C
from typing import Dict, List, Set, Tuple, Union
# 字典
d: Dict[str, int] = {"a": 1, "b": 2}
d1: Dict[str, int or str] = {"a": 1, "b": "2"} # 使用or表示支持多個型別
# 串列
l: List[int] = [1, 2, 3]
l1: List[int or str] = [1, 2, "3"]
# 元組
t: Tuple[str, int] = ("a", 1) # 代表了構成元組的第一個元素是 str 型別,第二個元素是 int 型別
t1: Tuple[str, ...] = ("a", "b", "c", "d", "e", "f", "g") # 代表接受多個 str 型別的元素
t2: Tuple[str or int, ...] = ("a", "b", 2) # 代表接受多個 str 或 int 型別的元素
# 集合
s: Set[int] = {1, 2, 3, 4}
s1: Set[Union[int, str, float]] = {1, "2", 3.333, 4} # Union 同 or
TypedDict
TypedDict宣告一個字典型別,該型別期望它的所有實體都有一組固定的keys,其中每個key都與對應型別的值關聯,
from typing import TypedDict
class Student(TypedDict):
name: str
age: int
height: float
s1: Student = {
"name": "xiao ming",
"age": 22,
"height": 55.5
}
s2: Student = {
"name": "xiao hong",
"age": 21,
}

可以看出,pycharm也會警告我們字典實體中缺失的key,
同時,在我們生成字典實體的時候,pycharm也會給我們key的提示,

型別別名
型別別名是通過將型別分配給別名來定義的,型別別名可用于簡化復雜型別提示,
from typing import Union
Number = Union[int, float]
def process(v: Number) -> Number:
return v
x: Number = 2
y: Number = 2.2
process(x)
process(22) # 型別檢查成功,型別別名和原始型別是等價的
NewType
使用NewType輔助類來創建不同的型別
from typing import NewType
Number = NewType("Number", int)
def process(v: Number) -> Number:
return v
x: Number = Number(22)
process(x)
process(22) # 型別檢查例外:Expected type 'Number', got 'int' instead
# 原因就是NewType創建的是原始型別的“子型別”
因此,型別別名 和 NewType 具體使用哪個,要視情況而定,不知道使用哪個,可以先使用型別別名,
NoReturn
當一個方法沒有回傳結果時,為了注解它的回傳型別,我們可以將其注解為 NoReturn,
因為Python 的函式運行結束時隱式回傳 None ,這和真正的無回傳值是有區別的,
from typing import NoReturn
def process() -> NoReturn:
pass
可選型別:Optional
使用 Optional[] 表示可能為 None 的值
from typing import Optional
def handler(x: int) -> Optional[int]:
if x % 2 == 0:
return x
可呼叫物件:Callable
若一個變數型別是可呼叫函式,則可以用 Callable[[Arg1Type, Arg2Type], ReturnType] 實作型別提示
from typing import Optional, Callable
def handler(x: int) -> Optional[int]:
if x % 2 == 0:
return x
def handler2(func: Callable[[int], Optional[int]]):
pass
handler2(handler)
字面量:Literal
指示相應的變數或函式引數只接收與提供的字面量(或多個字面量之一)等效的值,可以理解為規定了某個引數或變數的所有列舉值,
from typing import Literal, NoReturn
Mode = Literal["r", "w"]
def process(mode: Mode) -> NoReturn:
pass
process("s")

可以看出,pycharm檢查出了我們輸入的值并不符合字面量規定的值,進而出現了黃色警告,

Any
是一種特殊的型別,每種型別都視為與Any兼容,同樣,Any也與所有型別兼容,可以對Any型別的值執行任何操作或方法呼叫,并將其分配給任何變數,將Any型別的值分配給更精確的型別(more precise type)時,不會執行型別檢查,所有沒有回傳型別或引數型別的函式都將隱式地默認使用Any,
使用Any,說明值是動態型別,
把所有的型別都注解為 Any 將毫無意義,因此 Any 應當盡量少使用
from typing import Any
def foo() -> Any:
pass
抽象基類
# 在某些情況下,我們可能并不需要嚴格區分一個變數或引數到底是串列 list 型別還是元組 tuple 型別
# 可以使用一個更為泛化的型別,叫做 Sequence,其用法類似于 List
class typing.Sequence(Reversible[T_co], Collection[T_co])
# collections.abc.Iterator的泛型版本
# 注釋函式引數中的迭代型別時,推薦使用的抽象集合型別
class typing.Iterable(Generic[T_co])
def print_iterable(x: Iterable):
for i in x:
print(i)
# collections.abc.Mapping的泛型(generic)版本
# 注釋函式引數中的Key-Value型別時,推薦使用的抽象集合型別
class typing.Mapping(Sized, Collection[KT], Generic[VT_co])
泛型:TypeVar
先拋出問題:
假設有一個函式,要求它既能夠處理字串,又能夠處理數字,那么你可能很自然地想到了 Union ,如下:
from typing import Union
AddValue = https://www.cnblogs.com/iorson/archive/2023/02/12/Union[int, str]
def add(a: AddValue, b: AddValue) -> AddValue:
return a + b
if __name__ =="__main__":
print(add(1, 2)) # 型別檢查通過,輸出 3
print(add("1", "2")) # 型別檢查通過,輸出 12
print(add("1", 2)) # 型別檢查通過,報錯 TypeError: can only concatenate str (not "int") to str
在型別檢查通過的情況下,我們完成并運行了這段代碼,可是代碼卻報錯了!
原因就是我們的初衷是數字和數字相加實作求和,字串和字串相加實作拼接,沒有考慮到字串與數字混用的問題,從而引發錯誤,
根據以上問題,我們可以引入泛型來解決這個問題:
from typing import TypeVar
AddT = TypeVar("AddT", int, str)
def add(a: AddT, b: AddT) -> AddT:
return a + b
if __name__ == "__main__":
print(add(1, 2)) # 型別檢查通過,輸出 3
print(add("1", "2")) # 型別檢查通過,輸出 12
print(add("1", 2)) # 型別檢查失敗,pycharm告警 Expected type 'str' (matched generic type 'AddT'), got 'int' instead
"""
通過告警,我們提前發現了混用型別的問題,避免了程式運行時發生例外的可能,
"""
泛型很巧妙地對型別進行了引數化,同時又保留了函式處理不同型別時的靈活性,
參考
1、Python 標準庫 typing 型別注解標注
2、Python型別注解,你需要知道的都在這里了
本文來自博客園
作者:奧森iorson
轉載請注明原文鏈接:https://www.cnblogs.com/iorson/p/17114352.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/543660.html
標籤:其他
上一篇:Maven基礎學習
