您好,登錄后才能下訂單哦!
typing模塊怎么在Python中使用?很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。
實(shí)例引入
我們知道 Python 是一種動(dòng)態(tài)語言,在聲明一個(gè)變量時(shí)我們不需要顯式地聲明它的類型,例如下面的例子:
a = 2 print('1 + a =', 1 + a)
運(yùn)行結(jié)果:
1 + a = 3
這里我們首先聲明了一個(gè)變量 a,并將其賦值為了 2,然后將最后的結(jié)果打印出來,程序輸出來了正確的結(jié)果。但在這個(gè)過程中,我們沒有聲明它到底是什么類型。
但如果這時(shí)候我們將 a 變成一個(gè)字符串類型,結(jié)果會(huì)是怎樣的呢?改寫如下:
a = '2' print('1 + a =', 1 + a)
運(yùn)行結(jié)果:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
直接報(bào)錯(cuò)了,錯(cuò)誤原因是我們進(jìn)行了字符串類型的變量和數(shù)值類型變量的加和,兩種數(shù)據(jù)類型不同,是無法進(jìn)行相加的。
如果我們將上面的語句改寫成一個(gè)方法定義:
def add(a): return a + 1
這里定義了一個(gè)方法,傳入一個(gè)參數(shù),然后將其加 1 并返回。
如果這時(shí)候如果用下面的方式調(diào)用,傳入的參數(shù)是一個(gè)數(shù)值類型:
add(2)
則可以正常輸出結(jié)果 3。但如果我們傳入的參數(shù)并不是我們期望的類型,比如傳入一個(gè)字符類型,那么就會(huì)同樣報(bào)剛才類似的錯(cuò)誤。
但又由于 Python 的特性,很多情況下我們并不用去聲明它的類型,因此從方法定義上面來看,我們實(shí)際上是不知道一個(gè)方法的參數(shù)到底應(yīng)該傳入什么類型的。
這樣其實(shí)就造成了很多不方便的地方,在某些情況下一些復(fù)雜的方法,如果不借助于一些額外的說明,我們是不知道參數(shù)到底是什么類型的。
因此,Python 中的類型注解就顯得比較重要了。
類型注解
在 Python 3.5 中,Python PEP 484 引入了類型注解(type hints),在 Python 3.6 中,PEP 526 又進(jìn)一步引入了變量注解(Variable Annotations),所以上面的代碼我們改寫成如下寫法:
a: int = 2 print('5 + a =', 5 + a) def add(a: int) -> int: return a + 1
具體的語法是可以歸納為兩點(diǎn):
在聲明變量時(shí),變量的后面可以加一個(gè)冒號(hào),后面再寫上變量的類型,如 int、list 等等。
在聲明方法返回值的時(shí)候,可以在方法的后面加一個(gè)箭頭,后面加上返回值的類型,如 int、list 等等。
在PEP 8 中,具體的格式是這樣規(guī)定的:
在聲明變量類型時(shí),變量后方緊跟一個(gè)冒號(hào),冒號(hào)后面跟一個(gè)空格,再跟上變量的類型。
在聲明方法返回值的時(shí)候,箭頭左邊是方法定義,箭頭右邊是返回值的類型,箭頭左右兩邊都要留有空格。
有了這樣的聲明,以后我們?nèi)绻吹竭@個(gè)方法的定義,我們就知道傳入的參數(shù)類型了,如調(diào)用 add 方法的時(shí)候,我們就知道傳入的需要是一個(gè)數(shù)值類型的變量,而不是字符串類型,非常直觀。
但值得注意的是,這種類型和變量注解實(shí)際上只是一種類型提示,對(duì)運(yùn)行實(shí)際上是沒有影響的,比如調(diào)用 add 方法的時(shí)候,我們傳入的不是 int 類型,而是一個(gè) float 類型,它也不會(huì)報(bào)錯(cuò),也不會(huì)對(duì)參數(shù)進(jìn)行類型轉(zhuǎn)換,如:
add(1.5)
我們傳入的是一個(gè) float 類型的數(shù)值 1.5,看下運(yùn)行結(jié)果:
2.5
可以看到,運(yùn)行結(jié)果正常輸出,而且 1.5 并沒有經(jīng)過強(qiáng)制類型轉(zhuǎn)換變成 1,否則結(jié)果會(huì)變成 2。
因此,類型和變量注解只是提供了一種提示,對(duì)于運(yùn)行實(shí)際上沒有任何影響。
不過有了類型注解,一些 IDE 是可以識(shí)別出來并提示的,比如 PyCharm 就可以識(shí)別出來在調(diào)用某個(gè)方法的時(shí)候參數(shù)類型不一致,會(huì)提示 WARNING。
比如上面的調(diào)用,如果在 PyCharm 中,就會(huì)有如下提示內(nèi)容:
Expected type 'int', got 'float' instead
This inspection detects type errors in function call expressions. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Types of function parameters can be specified in docstrings or in Python 3 function annotations.
另外也有一些庫是支持類型檢查的,比如 mypy,安裝之后,利用 mypy 即可檢查出 Python 腳本中不符合類型注解的調(diào)用情況。
上面只是用一個(gè)簡(jiǎn)單的 int 類型做了實(shí)例,下面我們?cè)倏聪乱恍┫鄬?duì)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如列表、元組、字典等類型怎么樣來聲明。
可想而知了,列表用 list 表示,元組用 tuple 表示,字典用 dict 來表示,那么很自然地,在聲明的時(shí)候我們就很自然地寫成這樣了:
names: list = ['Germey', 'Guido'] version: tuple = (3, 7, 4) operations: dict = {'show': False, 'sort': True}
這么看上去沒有問題,確實(shí)聲明為了對(duì)應(yīng)的類型,但實(shí)際上并不能反映整個(gè)列表、元組的結(jié)構(gòu),比如我們只通過類型注解是不知道 names 里面的元素是什么類型的,只知道 names 是一個(gè)列表 list 類型,實(shí)際上里面都是字符串 str 類型。我們也不知道 version 這個(gè)元組的每一個(gè)元素是什么類型的,實(shí)際上是 int 類型。但這些信息我們都無從得知。因此說,僅僅憑借 list、tuple 這樣的聲明是非?!叭酢钡?,我們需要一種更強(qiáng)的類型聲明。
這時(shí)候我們就需要借助于 typing 模塊了,它提供了非?!皬?qiáng)“的類型支持,比如 List[str]、Tuple[int, int, int] 則可以表示由 str 類型的元素組成的列表和由 int 類型的元素組成的長(zhǎng)度為 3 的元組。所以上文的聲明寫法可以改寫成下面的樣子:
from typing import List, Tuple, Dict names: List[str] = ['Germey', 'Guido'] version: Tuple[int, int, int] = (3, 7, 4) operations: Dict[str, bool] = {'show': False, 'sort': True}
這樣一來,變量的類型便可以非常直觀地體現(xiàn)出來了。
目前 typing 模塊也已經(jīng)被加入到 Python 標(biāo)準(zhǔn)庫中,不需要安裝第三方模塊,我們就可以直接使用了。
typing
下面我們?cè)賮碓敿?xì)看下 typing 模塊的具體用法,這里主要會(huì)介紹一些常用的注解類型,如 List、Tuple、Dict、Sequence 等等,了解了每個(gè)類型的具體使用方法,我們可以得心應(yīng)手的對(duì)任何變量進(jìn)行聲明了。
在引入的時(shí)候就直接通過 typing 模塊引入就好了,例如:
from typing import List, Tuple
List
List、列表,是 list 的泛型,基本等同于 list,其后緊跟一個(gè)方括號(hào),里面代表了構(gòu)成這個(gè)列表的元素類型,如由數(shù)字構(gòu)成的列表可以聲明為:
var: List[int or float] = [2, 3.5]
另外還可以嵌套聲明都是可以的:
var: List[List[int]] = [[1, 2], [2, 3]]
Tuple、NamedTuple
Tuple、元組,是 tuple 的泛型,其后緊跟一個(gè)方括號(hào),方括號(hào)中按照順序聲明了構(gòu)成本元組的元素類型,如 Tuple[X, Y] 代表了構(gòu)成元組的第一個(gè)元素是 X 類型,第二個(gè)元素是 Y 類型。
比如想聲明一個(gè)元組,分別代表姓名、年齡、身高,三個(gè)數(shù)據(jù)類型分別為 str、int、float,那么可以這么聲明:
person: Tuple[str, int, float] = ('Mike', 22, 1.75)
同樣地也可以使用類型嵌套。
NamedTuple,是 collections.namedtuple 的泛型,實(shí)際上就和 namedtuple 用法完全一致,但個(gè)人其實(shí)并不推薦使用 NamedTuple,推薦使用 attrs 這個(gè)庫來聲明一些具有表征意義的類。
Dict、Mapping、MutableMapping
Dict、字典,是 dict 的泛型;Mapping,映射,是 collections.abc.Mapping 的泛型。根據(jù)官方文檔,Dict 推薦用于注解返回類型,Mapping 推薦用于注解參數(shù)。它們的使用方法都是一樣的,其后跟一個(gè)中括號(hào),中括號(hào)內(nèi)分別聲明鍵名、鍵值的類型,如:
def size(rect: Mapping[str, int]) -> Dict[str, int]: return {'width': rect['width'] + 100, 'height': rect['width'] + 100}
這里將 Dict 用作了返回值類型注解,將 Mapping 用作了參數(shù)類型注解。
MutableMapping 則是 Mapping 對(duì)象的子類,在很多庫中也經(jīng)常用 MutableMapping 來代替 Mapping。
Set、AbstractSet
Set、集合,是 set 的泛型;AbstractSet、是 collections.abc.Set 的泛型。根據(jù)官方文檔,Set 推薦用于注解返回類型,AbstractSet 用于注解參數(shù)。它們的使用方法都是一樣的,其后跟一個(gè)中括號(hào),里面聲明集合中元素的類型,如:
def describe(s: AbstractSet[int]) -> Set[int]: return set(s)
這里將 Set 用作了返回值類型注解,將 AbstractSet 用作了參數(shù)類型注解。
Sequence
Sequence,是 collections.abc.Sequence 的泛型,在某些情況下,我們可能并不需要嚴(yán)格區(qū)分一個(gè)變量或參數(shù)到底是列表 list 類型還是元組 tuple 類型,我們可以使用一個(gè)更為泛化的類型,叫做 Sequence,其用法類似于 List,如:
def square(elements: Sequence[float]) -> List[float]: return [x ** 2 for x in elements]
NoReturn
NoReturn,當(dāng)一個(gè)方法沒有返回結(jié)果時(shí),為了注解它的返回類型,我們可以將其注解為 NoReturn,例如:
def hello() -> NoReturn: print('hello')
Any
Any,是一種特殊的類型,它可以代表所有類型,靜態(tài)類型檢查器的所有類型都與 Any 類型兼容,所有的無參數(shù)類型注解和返回類型注解的都會(huì)默認(rèn)使用 Any 類型,也就是說,下面兩個(gè)方法的聲明是完全等價(jià)的:
def add(a): return a + 1 def add(a: Any) -> Any: return a + 1
原理類似于 object,所有的類型都是 object 的子類。但如果我們將參數(shù)聲明為 object 類型,靜態(tài)參數(shù)類型檢查便會(huì)拋出錯(cuò)誤,而 Any 則不會(huì),具體可以參考官方文檔的說明:https://docs.python.org/zh-cn/3/library/typing.html?highlight=typing#the-any-type。
TypeVar
TypeVar,我們可以借助它來自定義兼容特定類型的變量,比如有的變量聲明為 int、float、None 都是符合要求的,實(shí)際就是代表任意的數(shù)字或者空內(nèi)容都可以,其他的類型則不可以,比如列表 list、字典 dict 等等,像這樣的情況,我們可以使用 TypeVar 來表示。
例如一個(gè)人的身高,便可以使用 int 或 float 或 None 來表示,但不能用 dict 來表示,所以可以這么聲明:
height = 1.75 Height = TypeVar('Height', int, float, None) def get_height() -> Height: return height
這里我們使用 TypeVar 聲明了一個(gè) Height 類型,然后將其用于注解方法的返回結(jié)果。
NewType
NewType,我們可以借助于它來聲明一些具有特殊含義的類型,例如像 Tuple 的例子一樣,我們需要將它表示為 Person,即一個(gè)人的含義,但但從表面上聲明為 Tuple 并不直觀,所以我們可以使用 NewType 為其聲明一個(gè)類型,如:
Person = NewType('Person', Tuple[str, int, float]) person = Person(('Mike', 22, 1.75))
這里實(shí)際上 person 就是一個(gè) tuple 類型,我們可以對(duì)其像 tuple 一樣正常操作。
Callable
Callable,可調(diào)用類型,它通常用來注解一個(gè)方法,比如我們剛才聲明了一個(gè) add 方法,它就是一個(gè) Callable 類型:
print(Callable, type(add), isinstance(add, Callable))
運(yùn)行結(jié)果:
typing.Callable <class 'function'> True
在這里雖然二者 add 利用 type 方法得到的結(jié)果是 function,但實(shí)際上利用 isinstance 方法判斷確實(shí)是 True。
Callable 在聲明的時(shí)候需要使用 Callable[[Arg1Type, Arg2Type, ...], ReturnType] 這樣的類型注解,將參數(shù)類型和返回值類型都要注解出來,例如:
def date(year: int, month: int, day: int) -> str: return f'{year}-{month}-{day}' def get_date_fn() -> Callable[[int, int, int], str]: return date
這里首先聲明了一個(gè)方法 date,接收三個(gè) int 參數(shù),返回一個(gè) str 結(jié)果,get_date_fn 方法返回了這個(gè)方法本身,它的返回值類型就可以標(biāo)記為 Callable,中括號(hào)內(nèi)分別標(biāo)記了返回的方法的參數(shù)類型和返回值類型。
Union
Union,聯(lián)合類型,Union[X, Y] 代表要么是 X 類型,要么是 Y 類型。
聯(lián)合類型的聯(lián)合類型等價(jià)于展平后的類型:
Union[Union[int, str], float] == Union[int, str, float]
僅有一個(gè)參數(shù)的聯(lián)合類型會(huì)坍縮成參數(shù)自身,比如:
Union[int] == int
多余的參數(shù)會(huì)被跳過,比如:
Union[int, str, int] == Union[int, str]
在比較聯(lián)合類型的時(shí)候,參數(shù)順序會(huì)被忽略,比如:
Union[int, str] == Union[str, int]
這個(gè)在一些方法參數(shù)聲明的時(shí)候比較有用,比如一個(gè)方法,要么傳一個(gè)字符串表示的方法名,要么直接把方法傳過來:
def process(fn: Union[str, Callable]): if isinstance(fn, str): # str2fn and process pass elif isinstance(fn, Callable): fn()
這樣的聲明在一些類庫方法定義的時(shí)候十分常見。
Optional
Optional,意思是說這個(gè)參數(shù)可以為空或已經(jīng)聲明的類型,即 Optional[X] 等價(jià)于 Union[X, None]。
但值得注意的是,這個(gè)并不等價(jià)于可選參數(shù),當(dāng)它作為參數(shù)類型注解的時(shí)候,不代表這個(gè)參數(shù)可以不傳遞了,而是說這個(gè)參數(shù)可以傳為 None。
如當(dāng)一個(gè)方法執(zhí)行結(jié)果,如果執(zhí)行完畢就不返回錯(cuò)誤信息, 如果發(fā)生問題就返回錯(cuò)誤信息,則可以這么聲明:
def judge(result: bool) -> Optional[str]: if result: return 'Error Occurred'
Generator
如果想代表一個(gè)生成器類型,可以使用 Generator,它的聲明比較特殊,其后的中括號(hào)緊跟著三個(gè)參數(shù),分別代表 YieldType、SendType、ReturnType,如:
def echo_round() -> Generator[int, float, str]: sent = yield 0 while sent >= 0: sent = yield round(sent) return 'Done'
在這里 yield 關(guān)鍵字后面緊跟的變量的類型就是 YieldType,yield 返回的結(jié)果的類型就是 SendType,最后生成器 return 的內(nèi)容就是 ReturnType。
當(dāng)然很多情況下,生成器往往只需要 yield 內(nèi)容就夠了,我們是不需要 SendType 和 ReturnType 的,可以將其設(shè)置為空,如:
def infinite_stream(start: int) -> Generator[int, None, None]: while True: yield start start += 1
案例實(shí)戰(zhàn)
接下來讓我們看一個(gè)實(shí)際的項(xiàng)目,看看經(jīng)常用到的類型一般是怎么使用的。
這里我們看的庫是 requests-html,是由 Kenneth Reitz 所開發(fā)的,其 GitHub 地址為:https://github.com/psf/requests-html,下面我們主要看看它的源代碼中一些類型是如何聲明的。
這個(gè)庫的源代碼其實(shí)就一個(gè)文件,那就是 https://github.com/psf/requests-html/blob/master/requests_html.py,我們看一下它里面的一些 typing 的定義和方法定義。
首先 Typing 的定義部分如下:
from typing import Set, Union, List, MutableMapping, Optional _Find = Union[List['Element'], 'Element'] _XPath = Union[List[str], List['Element'], str, 'Element'] _Result = Union[List['Result'], 'Result'] _HTML = Union[str, bytes] _BaseHTML = str _UserAgent = str _DefaultEncoding = str _URL = str _RawHTML = bytes _Encoding = str _LXML = HtmlElement _Text = str _Search = Result _Containing = Union[str, List[str]] _Links = Set[str] _Attrs = MutableMapping _Next = Union['HTML', List[str]] _NextSymbol = List[str]
這里可以看到主要用到的類型有 Set、Union、List、MutableMapping、Optional,這些在上文都已經(jīng)做了解釋,另外這里使用了多次 Union 來聲明了一些新的類型,如 _Find 則要么是是 Element 對(duì)象的列表,要么是單個(gè) Element 對(duì)象,_Result 則要么是 Result 對(duì)象的列表,要么是單個(gè) Result 對(duì)象。另外 _Attrs 其實(shí)就是字典類型,這里用 MutableMapping 來表示了,沒有用 Dict,也沒有用 Mapping。
接下來再看一個(gè) Element 類的聲明:
class Element(BaseParser): """An element of HTML. :param element: The element from which to base the parsing upon. :param url: The URL from which the HTML originated, used for ``absolute_links``. :param default_encoding: Which encoding to default to. """ __slots__ = [ 'element', 'url', 'skip_anchors', 'default_encoding', '_encoding', '_html', '_lxml', '_pq', '_attrs', 'session' ] def __init__(self, *, element, url: _URL, default_encoding: _DefaultEncoding = None) -> None: super(Element, self).__init__(element=element, url=url, default_encoding=default_encoding) self.element = element self.tag = element.tag self.lineno = element.sourceline self._attrs = None def __repr__(self) -> str: attrs = ['{}={}'.format(attr, repr(self.attrs[attr])) for attr in self.attrs] return "<Element {} {}>".format(repr(self.element.tag), ' '.join(attrs)) @property def attrs(self) -> _Attrs: """Returns a dictionary of the attributes of the :class:`Element <Element>` (`learn more <https://www.w3schools.com/tags/ref_attributes.asp>`_). """ if self._attrs is None: self._attrs = {k: v for k, v in self.element.items()} # Split class and rel up, as there are ussually many of them: for attr in ['class', 'rel']: if attr in self._attrs: self._attrs[attr] = tuple(self._attrs[attr].split()) return self._attrs
這里 __init__ 方法接收非常多的參數(shù),同時(shí)使用 _URL 、_DefaultEncoding 進(jìn)行了參數(shù)類型注解,另外 attrs 方法使用了 _Attrs 進(jìn)行了返回結(jié)果類型注解。
整體看下來,每個(gè)參數(shù)的類型、返回值都進(jìn)行了清晰地注解,代碼可讀性大大提高。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。