Python中typing模塊詳解
簡介
Python是一門動態(tài)語言,很多時(shí)候我們可能不清楚函數(shù)參數(shù)類型或者返回值類型,很有可能導(dǎo)致一些類型沒有指定方法,在寫完代碼一段時(shí)間后回過頭看代碼,很可能忘記了自己寫的函數(shù)需要傳什么參數(shù),返回什么類型的結(jié)果法牲,就不得不去閱讀代碼的具體內(nèi)容,降低了閱讀的速度琼掠,typing模塊可以很好的解決這個(gè)問題拒垃。
Python 運(yùn)行時(shí)并不強(qiáng)制標(biāo)注函數(shù)和變量類型。類型標(biāo)注可被用于第三方工具瓷蛙,比如類型檢查器悼瓮、集成開發(fā)環(huán)境、靜態(tài)檢查器等艰猬。
自python3.5開始横堡,PEP484為python引入了類型注解(type hints),typing的主要作用有:
類型檢查冠桃,防止運(yùn)行時(shí)出現(xiàn)參數(shù)命贴、返回值類型不符。
作為開發(fā)文檔附加說明食听,方便使用者調(diào)用時(shí)傳入和返回參數(shù)類型胸蛛。
模塊加入不會影響程序的運(yùn)行不會報(bào)正式的錯誤,pycharm支持typing檢查錯誤時(shí)會出現(xiàn)黃色警告樱报。
別名和NewType
1. 類型別名
要定義一個(gè)類型別名胚泌,可以將一個(gè)類型賦給別名。類型別名可用于簡化復(fù)雜類型簽名肃弟,在下面示例中,Vector
和 list[float]
將被視為可互換的同義詞:
Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# typechecks; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
請注意零蓉,
None
作為類型提示是一種特殊情況笤受,并且由type(None)
取代,這是因?yàn)?code>None是一個(gè)存在于解釋器中的單例對象敌蜂。
2. NewType
使用 NewType
輔助函數(shù)創(chuàng)建不同的類型箩兽,靜態(tài)類型檢查器會將新類型視為它是原始類型的子類。
from typing import NewType
UserId = NewType('UserId', int)
def get_user_name(user_id: UserId) -> str:
...
# typechecks
user_a = get_user_name(UserId(42351))
# does not typecheck; an int is not a UserId
user_b = get_user_name(-1)
仍然可以對 UserId
類型的變量執(zhí)行所有的 int
支持的操作章喉,但結(jié)果將始終為 int
類型汗贫。這可以讓你在需要 int
的地方傳入 UserId
身坐,但會阻止你以無效的方式無意中創(chuàng)建 UserId
:
# 'output' is of type 'int', not 'UserId'
output = UserId(23413) + UserId(54341)
需要注意,這些檢查僅通過靜態(tài)類型檢查程序來強(qiáng)制落包。
NewType
返回的是一個(gè)函數(shù)該函數(shù)立即返回傳遞它的任意值這就意味著UserId(1234)
并不會創(chuàng)建一個(gè)新的類或引入任何超出常規(guī)函數(shù)調(diào)用的開銷部蛇。
因此,運(yùn)行過程中same_value is Newtype("TypeName", Base)(same_value)
始終為True咐蝇。
但是涯鲁,可以基于NewType
創(chuàng)建NewType
。
使用類型別名聲明兩種類型彼此 等效 有序。
Alias = Original
將使靜態(tài)類型檢查對待所有情況下Alias
完全等同于Original
抹腿。當(dāng)您想簡化復(fù)雜類型簽名時(shí),這很有用旭寿。
相反警绩,NewType
聲明一種類型是另一種類型的子類型。Derived = NewType('Derived', Original)
將使靜態(tài)類型檢查器將Derived
當(dāng)作Original
的 子類 盅称,這意味著Original
類型的值不能用于Derived
類型的值需要的地方肩祥。當(dāng)您想以最小的運(yùn)行時(shí)間成本防止邏輯錯誤時(shí),這非常有用微渠。
常用類型
typing模塊最基本的支持由 Any
搭幻,Tuple
,Callable
逞盆,TypeVar
和 Generic
類型組成檀蹋。
1. 泛型集合類型
class typing.List
(list, MutableSequence[T])
list的泛型版本。用于注釋返回類型云芦。要注釋參數(shù)俯逾,最好使用抽象集合類型,如Sequence或Iterable舅逸。示例:
T = TypeVar('T', int, float)
def vec2(x: T, y: T) -> List[T]:
return [x, y]
def keep_positives(vector: Sequence[T]) -> List[T]:
return [item for item in vector if item > 0]
class typing.Dict
(dict, MutableMapping[KT, VT])
dict 的泛型版本桌肴。對標(biāo)注返回類型比較有用。如果要標(biāo)注參數(shù)的話琉历,使用如 Mapping 的抽象容器類型是更好的選擇坠七。示例:
def count_words(text: str) -> Dict[str, int]:
...
類似的類型還有
class typing.Set
(set, MutableSet[T])
2. 抽象基類
class typing.Iterable
(Generic[T_co])
要注釋函數(shù)參數(shù)中的迭代類型時(shí),推薦使用的抽象集合類型旗笔。
class typing.Sequence
(Reversible[T_co], Collection[T_co])
要注釋函數(shù)參數(shù)中的序列例如列表類型時(shí)彪置,推薦使用的抽象集合類型。
class typing.Mapping
(Sized, Collection[KT], Generic[VT_co])
要注釋函數(shù)參數(shù)中的Key-Value類型時(shí)蝇恶,推薦使用的抽象集合類型拳魁。
3. 泛型
class typing.TypeVar
類型變量。
需要注意的是 TypeVar
不是一個(gè)類使用 isinstance(x, T)
會在運(yùn)行時(shí)拋出 TypeError
異常撮弧。一般地說潘懊, isinstance()
和 issubclass()
不應(yīng)該和類型變量一起使用姚糊。示例:
T = TypeVar('T') # Can be anything
A = TypeVar('A', str, bytes) # Must be str or bytes
def repeat(x: T, n: int) -> Sequence[T]:
"""Return a list containing n references to x."""
return [x]*n
def longest(x: A, y: A) -> A:
"""Return the longest of two strings."""
return x if len(x) >= len(y) else y
typing.AnyStr
AnyStr是一個(gè)字符串和字節(jié)類型的特殊類型變量AnyStr = TypeVar('AnyStr', str, bytes)
,它用于可以接受任何類型的字符串而不允許不同類型的字符串混合的函數(shù)授舟。
def concat(a: AnyStr, b: AnyStr) -> AnyStr:
return a + b
concat(u"foo", u"bar") # Ok, output has type 'unicode'
concat(b"foo", b"bar") # Ok, output has type 'bytes'
concat(u"foo", b"bar") # Error, cannot mix unicode and bytes
class typing.Generic
泛型的抽象基類型救恨,泛型類型通常通過繼承具有一個(gè)或多個(gè)類型變量的該類的實(shí)例來聲明。
泛型類型可以有任意數(shù)量的類型變量岂却,并且類型變量可能會受到限制忿薇。
每個(gè)參數(shù)的類型變量必須是不同的。
X = TypeVar('X')
Y = TypeVar('Y')
class Mapping(Generic[KT, VT]):
def __getitem__(self, key: KT) -> VT: ...
def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
try:
return mapping[key]
except KeyError:
return default
- 可以對
Generic
使用多重繼承躏哩。
from collections.abc import Sized
from typing import TypeVar, Generic
T = TypeVar('T')
class LinkedList(Sized, Generic[T]): ...
- 從泛型類繼承時(shí)署浩,某些類型變量可能是固定的。
from collections.abc import Mapping
from typing import TypeVar
T = TypeVar('T')
class MyDict(Mapping[str, T]): ...
4. 特殊類型
typing.Any
特殊類型扫尺,表明類型沒有任何限制筋栋。
每一個(gè)類型都對
Any
兼容。Any
對每一個(gè)類型都兼容正驻。
Any
是一種特殊的類型弊攘。靜態(tài)類型檢查器將所有類型視為與Any
兼容,反之亦然姑曙, Any
也與所有類型相兼容襟交。
這意味著可對類型為 Any
的值執(zhí)行任何操作或者方法調(diào)用并將其賦值給任意變量。如下所示伤靠,將 Any
類型的值賦值給另一個(gè)更具體的類型時(shí)捣域,Python不會執(zhí)行類型檢查。例如宴合,當(dāng)把 a
賦值給 s
時(shí)焕梅,即使 s
被聲明為 str
類型,在運(yùn)行時(shí)接收到的是 int
值卦洽,靜態(tài)類型檢查器也不會報(bào)錯
from typing import Any
a = None # type: Any
a = [] # OK
a = 2 # OK
s = '' # type: str
s = a # OK
def foo(item: Any) -> int:
# Typechecks; 'item' could be any type,
# and that type might have a 'bar' method
item.bar()
...
所有返回值無類型或形參無類型的函數(shù)將隱式地默認(rèn)使用Any
類型贞言,如下所示2種寫法等效。
def legacy_parser(text):
...
return data
# A static type checker will treat the above
# as having the same signature as:
def legacy_parser(text: Any) -> Any:
...
return data
Any
和 object
的行為對比阀蒂。與Any
相似该窗,所有的類型都是object
的子類型。然而不同于 Any
蚤霞,反之并不成立:object
不是 其他所有類型的子類型酗失。
這意味著當(dāng)一個(gè)值的類型是object
的時(shí)候,類型檢查器會拒絕對它的幾乎所有的操作争便。把它賦值給一個(gè)指定了類型的變量(或者當(dāng)作返回值)是一個(gè)類型錯誤。比如說,下述代碼hash_a
會被IDE標(biāo)注不能從object
找到magic
的引用錯誤断医,而hash_b則不會:
def hash_a(item: object) -> int:
# Fails; an object does not have a 'magic' method.
item.magic()
def hash_b(item: Any) -> int:
# Typechecks
item.magic()
# Typechecks, since ints and strs are subclasses of objecthash_a(42)
hash_a("foo")
# Typechecks, since Any is compatible with all typeshash_b(42)
hash_b("foo")
typing.NoReturn
標(biāo)記一個(gè)函數(shù)沒有返回值的特殊類型滞乙。
from typing import NoReturn
def stop() -> NoReturn:
raise RuntimeError
5. 特殊形式
class typing.Type
(Generic[CT_co])
一個(gè)注解為 C
的變量可以接受一個(gè)類型為 C
的值奏纪。相對地,一個(gè)注解為 Type[C]
的變量可以接受本身為類的值 斩启。 更精確地說它接受 C
的 類對象 序调,例如:
a = 3 # Has type 'int'
b = int # Has type 'Type[int]'
c = type(a) # Also has type 'Type[int]'
注意Type[C]
是協(xié)變的:
class User: ...
class BasicUser(User): ...
class ProUser(User): ...
class TeamUser(User): ...
# Accepts User, BasicUser, ProUser, TeamUser, ...
def make_new_user(user_class: Type[User]) -> User:
# ...
return user_class()
typing.Tuple
元組類型,Tuple[X, Y]
標(biāo)注了一個(gè)二元組類型,其第一個(gè)元素的類型為 X
且第二個(gè)元素的類型為Y
兔簇》⒕睿空元組的類型可寫作 Tuple[()]
為表達(dá)一個(gè)同類型元素的變長元組,使用省略號字面量垄琐,如Tuple[int, ...]
边酒。單獨(dú)的一個(gè) Tuple
等價(jià)于 Tuple[Any, ...]
,進(jìn)而等價(jià)于tuple
狸窘。
示例: Tuple[int, float, str]
表示一個(gè)由整數(shù)墩朦、浮點(diǎn)數(shù)和字符串組成的三元組。
typing.Union
聯(lián)合類型翻擒;Union[X, Y]
意味著:要么是 X
氓涣,要么就是 Y
。定義一個(gè)聯(lián)合類型陋气,需要注意的有:
參數(shù)必須是類型劳吠,而且必須至少有一個(gè)參數(shù)。
能繼承或者實(shí)例化一個(gè)聯(lián)合類型巩趁。
Union[X, Y]
不能寫成Union[X][Y]
痒玩。可以使用
Optional[X]
作為Union[X, None]
的縮寫- 聯(lián)合類型的聯(lián)合類型會被展開打平,比如
Union[Union[int, str], float] == Union[int, str, float]
- 僅有一個(gè)參數(shù)的聯(lián)合類型會坍縮成參數(shù)自身晶渠,比如:
Union[int] == int # The constructor actually returns int
- 多余的參數(shù)會被跳過凰荚,比如:
Union[int, str, int] == Union[int, str]
- 在比較聯(lián)合類型的時(shí)候,參數(shù)順序會被忽略褒脯,比如:
Union[int, str] == Union[str, int]
typing.Optional
可選類型便瑟。Optional[X]
等價(jià)于Union[X, None]
。
def sqrt(x: Union[int, float])->Optional[float]:
if x >= 0:
return math.sqrt(x)
typing.Callable
可調(diào)用類型番川;Callable[[int], str]
是一個(gè)函數(shù)到涂,接受一個(gè) int
參數(shù),返回一個(gè)str
颁督。下標(biāo)值的語法必須恰為兩個(gè)值:參數(shù)列表和返回類型践啄。參數(shù)列表必須是一個(gè)類型和省略號組成的列表;返回值必須是單一一個(gè)類型沉御。
不存在語法來表示可選的或關(guān)鍵詞參數(shù)屿讽,這類函數(shù)類型罕見用于回調(diào)函數(shù)。Callable[..., ReturnType]
(使用字面省略號)能被用于提示一個(gè)可調(diào)用對象,接受任意數(shù)量的參數(shù)并且返回 ReturnType
伐谈。單獨(dú)的 Callable
等價(jià)于Callable[..., Any]
烂完,并且進(jìn)而等價(jià)于 collections.abc.Callable
。