python Typing模块-类型注解
寫在篇前
??typing 是python3.5中開始新增的專用于類型注解(type hints)的模塊,為python程序提供靜態(tài)類型檢查,如下面的greeting函數(shù)規(guī)定了參數(shù)name的類型是str,返回值的類型也是str。
def greeting(name: str) -> str:return 'Hello ' + name??在實踐中,該模塊常用的類型有 Any, Union, Tuple, Callable, TypeVar,Optional和Generic等,本篇博客主要依據(jù)官方文檔以及日常使用經(jīng)驗來探討一下typing模塊的使用方法以及經(jīng)驗。
注意事項:typing模塊雖然已經(jīng)正式加入到了標準庫中,但是如果核心開發(fā)者認為有必要的話,api也可能會發(fā)生改變,即不保證向后兼容性
Type aliases
??簡單的類型注解及其形式如開篇例子所示,那么除了默認的int、str等簡單類型,就可以通過typing模塊來實現(xiàn)注解。首先,我們可以通過給類型賦予別名,簡化類型注釋,如下例中的Vector和List[float]是等價的。
from typing import List Vector = List[float]def scale(scalar: float, vector: Vector) -> Vector:return [scalar * num for num in vector]??上面的例子,似乎不能很好的體現(xiàn)類型注釋別名的優(yōu)勢,官網(wǎng)還給了另外一個例子,非常生動形象:
from typing import Dict, Tuple, SequenceConnectionOptions = Dict[str, str] Address = Tuple[str, int] Server = Tuple[Address, ConnectionOptions]def broadcast_message(message: str, servers: Sequence[Server]) -> None:passdef broadcast_message2(message: str,servers: Sequence[Tuple[Tuple[str, int], Dict[str, str]]]) -> None:pass??毫無疑問,函數(shù)broadcast_message2的注解明顯比broadcast_message更加簡潔清晰。
NewType
??可以使用NewType來創(chuàng)建一個用戶自定義類型,如:
from typing import NewTypeUserId = NewType("UserId", int) def get_user_name(user_id: UserId) -> str:pass# 可以通過類型檢查 user_a = get_user_name(UserId(42351)) # 不能夠通過類型檢查 user_b = get_user_name(-1)??NewType的實現(xiàn)方式很簡單,在運行時,NewType(name, tp)返回一個函數(shù),這個函數(shù)返回其原本的值。靜態(tài)類型檢查器會將新類型看作是原始類型的一個子類。
# NewType實現(xiàn)代碼 def NewType(name, tp):def new_type(x):return xnew_type.__name__ = namenew_type.__supertype__ = tpreturn new_type??因為NewType被看做原始類型的子類,因此在新類型上你可以進行原始類型允許的操作,且結(jié)果的類型是原始類型,看起來是不是很神奇,甚至有點繞!其實關(guān)鍵還是要理解其原理,舉兩個例子:
-
實例1
>>> user_id_1 = UserId(23) >>> user_id_2 = UserId(46) >>> user_id_1 + user_id_2 69 -
實例2
# 請和NewType第一個例子對比 def get_num(num: int) -> int:return num# 可以通過類型檢查 get_num(1)user_id: UserId = UserId(23) # 可以通過類型檢查 get_num(user_id)
??
??請注意,這些檢查僅會被靜態(tài)類型檢查程序強制執(zhí)行。在運行時,Derived = NewType('Derived',Base) 將 Derived 一個函數(shù),該函數(shù)立即返回傳遞給它的任何參數(shù)。這意味著表達式 Derived(some_value) 不會創(chuàng)建一個新的類或引入任何超出常規(guī)函數(shù)調(diào)用的開銷。更確切地說,表達式 some_value is Derived(some_value) 在運行時總是為真。這也意味著無法創(chuàng)建 Derived 的子類型,因為它是運行時的標識函數(shù),而不是實際的類型。
Callable
??回調(diào)函數(shù)可以使用類似Callable[[Arg1Type, Arg2Type],ReturnType]的類型注釋,這個比較簡單,例子如下,如果只指定回調(diào)函數(shù)的返回值類型,則可以使用Callable[..., ReturnType]的形式:
from typing import Callabledef async_query(on_success: Callable[[int], None],on_error: Callable[[int, Exception], None]) -> None:passGenerics
??由于無法以通用的方式靜態(tài)推斷有關(guān)保存在容器(list set tuple)中對象的類型信息,因此抽象類被用來拓展表示容器中的元素。如下面子里中,使用基類Employee來擴展其可能得子類如 Sub1_Employee、Sub2_Employee等。但是其局限性明顯,所以我們需要引入泛型(generics)。
from typing import Mapping, Sequencedef notify_by_email(employees: Sequence[Employee],overrides: Mapping[str, str]) -> None:pass??可以通過typing中的TypeVar將泛型參數(shù)化,如:
from typing import Sequence, TypeVarT = TypeVar('T') # Can be anything A = TypeVar('A', str, bytes) # Must be str or bytesdef first(l: Sequence[T]) -> T: # Generic functionreturn l[0]User-defined generic types
??可以將用戶字定義的類定義為泛型類:
from typing import TypeVar, Generic from logging import LoggerT = TypeVar('T')class LoggedVar(Generic[T]):def __init__(self, value: T, name: str, logger: Logger) -> None:self.name = nameself.logger = loggerself.value = valuedef set(self, new: T) -> None:self.log('Set ' + repr(self.value))self.value = newdef get(self) -> T:self.log('Get ' + repr(self.value))return self.valuedef log(self, message: str) -> None:self.logger.info('%s: %s', self.name, message)??Generic[T] 作為基類定義了類 LoggedVar 采用單個類型參數(shù) T。這也使得 T 作為類體內(nèi)的一個類型有效。通過Generic基類使用元類(metaclass)定義__getitem__()使得LoggedVar[t]是有效類型:
from typing import Iterabledef zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:for var in vars:var.set(0)??泛型類型可以有任意數(shù)量的類型變量,并且類型變量可能會受到限制:
from typing import TypeVar, GenericT = TypeVar('T') S = TypeVar('S', int, str)class StrangePair(Generic[T, S]):passAny Type
??Any是一種特殊的類型,靜態(tài)類型檢查器視Any與任何類型兼容,任何類型與Any兼容。
def foo(item: Any) -> int:item.bar()寫在篇后
??在實際使用中, Any, Union, Tuple, List, Sequence, Mapping, Callable, TypeVar,Optional, Generic等的使用頻率比較高,其中Union、Optional、Sequence、Mapping非常有用,注意掌握。
-
Union
即并集,所以Union[X, Y] 意思是要么X類型、要么Y類型
-
Optional
Optional[X]與Union[X, None],即它默認允許None類型
-
Sequence
即序列,需要注意的是,List一般用來標注返回值;Sequence、Iterable用來標注參數(shù)類型
-
Mapping
即字典,需要注意的是,Dict一般用來標注返回值;Mapping用來標注參數(shù)類型
??貼一段類型標注的實例代碼,是不是讓人一目了然,不需要看具體代碼邏輯就知道參數(shù)類型以及如何調(diào)用呢?
def __init__(self,X: Optional[Union[np.ndarray, sparse.spmatrix, pd.DataFrame]] = None,obs: Optional[Union[pd.DataFrame, Mapping[str, Iterable[Any]]]] = None,var: Optional[Union[pd.DataFrame, Mapping[str, Iterable[Any]]]] = None,uns: Optional[Mapping[str, Any]] = None,obsm: Optional[Union[np.ndarray, Mapping[str, Sequence[Any]]]] = None,varm: Optional[Union[np.ndarray, Mapping[str, Sequence[Any]]]] = None,layers: Optional[Mapping[str, Union[np.ndarray, sparse.spmatrix]]] = None,raw: Optional[Raw] = None,dtype: Union[np.dtype, str] = 'float32',shape: Optional[Tuple[int, int]] = None,filename: Optional[PathLike] = None,filemode: Optional[str] = None,asview: bool = False,*, oidx: Index = None, vidx: Index = None):??
??類型標注可以使程序的維護性、使用性更高,這一點非常重要;另外,許多IDE配合類型標注可以增強智能提示功能,加快編碼速度,提高效率,我們何樂而不為呢?
總結(jié)
以上是生活随笔為你收集整理的python Typing模块-类型注解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python meataclass详解
- 下一篇: python Logging日志记录模块