使用類型注解讓 Python 代碼更易讀

      網(wǎng)友投稿 810 2025-04-04

      我們知道 Python 是一種動態(tài)語言,在聲明一個變量時我們不需要顯式地聲明它的類型,例如下面的例子:

      a?=?2

      print('1?+?a?=',?1?+?a)

      運行結(jié)果:

      1?+?a?=?3

      這里我們首先聲明了一個變量?a,并將其賦值為了 2,然后將最后的結(jié)果打印出來,程序輸出來了正確的結(jié)果。但在這個過程中,我們沒有聲明它到底是什么類型。

      但如果這時候我們將?a?變成一個字符串類型,結(jié)果會是怎樣的呢?改寫如下:

      a?=?'2'

      print('1?+?a?=',?1?+?a)

      運行結(jié)果:

      TypeError:?unsupported?operand?type(s)?for?+:?'int'?and?'str'

      直接報錯了,錯誤原因是我們進(jìn)行了字符串類型的變量和數(shù)值類型變量的加和,兩種數(shù)據(jù)類型不同,是無法進(jìn)行相加的。

      如果我們將上面的語句改寫成一個方法定義:

      def?add(a):

      return?a?+?1

      這里定義了一個方法,傳入一個參數(shù),然后將其加 1 并返回。

      如果這時候如果用下面的方式調(diào)用,傳入的參數(shù)是一個數(shù)值類型:

      add(2)

      則可以正常輸出結(jié)果 3。但如果我們傳入的參數(shù)并不是我們期望的類型,比如傳入一個字符類型,那么就會同樣報剛才類似的錯誤。

      但又由于 Python 的特性,很多情況下我們并不用去聲明它的類型,因此從方法定義上面來看,我們實際上是不知道一個方法的參數(shù)到底應(yīng)該傳入什么類型的。

      這樣其實就造成了很多不方便的地方,在某些情況下一些復(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

      具體的語法是可以歸納為兩點:

      在聲明變量時,變量的后面可以加一個冒號,后面再寫上變量的類型,如 ?int、list 等等。

      在聲明方法返回值的時候,可以在方法的后面加一個箭頭,后面加上返回值的類型,如 int、list 等等。

      在 PEP 8 中,具體的格式是這樣規(guī)定的:

      在聲明變量類型時,變量后方緊跟一個冒號,冒號后面跟一個空格,再跟上變量的類型。

      在聲明方法返回值的時候,箭頭左邊是方法定義,箭頭右邊是返回值的類型,箭頭左右兩邊都要留有空格。

      有了這樣的聲明,以后我們?nèi)绻吹竭@個方法的定義,我們就知道傳入的參數(shù)類型了,如調(diào)用 add 方法的時候,我們就知道傳入的需要是一個數(shù)值類型的變量,而不是字符串類型,非常直觀。

      但值得注意的是,這種類型和變量注解實際上只是一種類型提示,對運行實際上是沒有影響的,比如調(diào)用 add 方法的時候,我們傳入的不是 int 類型,而是一個 float 類型,它也不會報錯,也不會對參數(shù)進(jìn)行類型轉(zhuǎn)換,如:

      add(1.5)

      我們傳入的是一個 float 類型的數(shù)值 1.5,看下運行結(jié)果:

      2.5

      可以看到,運行結(jié)果正常輸出,而且 1.5 并沒有經(jīng)過強制類型轉(zhuǎn)換變成 1,否則結(jié)果會變成 2。

      因此,類型和變量注解只是提供了一種提示,對于運行實際上沒有任何影響。

      不過有了類型注解,一些 IDE 是可以識別出來并提示的,比如 PyCharm 就可以識別出來在調(diào)用某個方法的時候參數(shù)類型不一致,會提示 WARNING。

      比如上面的調(diào)用,如果在 PyCharm 中,就會有如下提示內(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)用情況。

      上面只是用一個簡單的 int 類型做了實例,下面我們再看下一些相對復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如列表、元組、字典等類型怎么樣來聲明。

      可想而知了,列表用 list 表示,元組用 tuple 表示,字典用 dict 來表示,那么很自然地,在聲明的時候我們就很自然地寫成這樣了:

      names:?list?=?['Germey',?'Guido']

      version:?tuple?=?(3,?7,?4)

      operations:?dict?=?{'show':?False,?'sort':?True}

      這么看上去沒有問題,確實聲明為了對應(yīng)的類型,但實際上并不能反映整個列表、元組的結(jié)構(gòu),比如我們只通過類型注解是不知道 names 里面的元素是什么類型的,只知道 names 是一個列表 list 類型,實際上里面都是字符串 str 類型。我們也不知道 version 這個元組的每一個元素是什么類型的,實際上是 int 類型。但這些信息我們都無從得知。因此說,僅僅憑借 list、tuple 這樣的聲明是非常“弱”的,我們需要一種更強的類型聲明。

      這時候我們就需要借助于 typing 模塊了,它提供了非常“強“的類型支持,比如List[str]、Tuple[int, int, int]?則可以表示由 str 類型的元素組成的列表和由 int 類型的元素組成的長度為 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

      下面我們再來詳細(xì)看下 typing 模塊的具體用法,這里主要會介紹一些常用的注解類型,如 List、Tuple、Dict、Sequence 等等,了解了每個類型的具體使用方法,我們可以得心應(yīng)手的對任何變量進(jìn)行聲明了。

      在引入的時候就直接通過 typing 模塊引入就好了,例如:

      from?typing?import?List,?Tuple

      List

      List、列表,是 list 的泛型,基本等同于 list,其后緊跟一個方括號,里面代表了構(gòu)成這個列表的元素類型,如由數(shù)字構(gòu)成的列表可以聲明為:

      var:?List[int?or?float]?=?[2,?3.5]

      另外還可以嵌套聲明都是可以的:

      var:?List[List[int]]?=?[[1,?2],?[2,?3]]

      Tuple、NamedTuple

      Tuple、元組,是 tuple 的泛型,其后緊跟一個方括號,方括號中按照順序聲明了構(gòu)成本元組的元素類型,如?Tuple[X, Y]?代表了構(gòu)成元組的第一個元素是 X 類型,第二個元素是 Y 類型。

      比如想聲明一個元組,分別代表姓名、年齡、身高,三個數(shù)據(jù)類型分別為 str、int、float,那么可以這么聲明:

      person:?Tuple[str,?int,?float]?=?('Mike',?22,?1.75)

      同樣地也可以使用類型嵌套。

      NamedTuple,是 collections.namedtuple 的泛型,實際上就和 namedtuple 用法完全一致,但個人其實并不推薦使用 NamedTuple,推薦使用 attrs 這個庫來聲明一些具有表征意義的類。

      Dict、Mapping、MutableMapping

      Dict、字典,是 dict 的泛型;Mapping,映射,是 collections.abc.Mapping 的泛型。根據(jù)官方文檔,Dict 推薦用于注解返回類型,Mapping 推薦用于注解參數(shù)。它們的使用方法都是一樣的,其后跟一個中括號,中括號內(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 對象的子類,在很多庫中也經(jīng)常用 MutableMapping 來代替 Mapping。

      Set、AbstractSet

      Set、集合,是 set 的泛型;AbstractSet、是 collections.abc.Set 的泛型。根據(jù)官方文檔,Set 推薦用于注解返回類型,AbstractSet 用于注解參數(shù)。它們的使用方法都是一樣的,其后跟一個中括號,里面聲明集合中元素的類型,如:

      def?describe(s:?AbstractSet[int])?->?Set[int]:

      return?set(s)

      這里將 Set 用作了返回值類型注解,將 AbstractSet 用作了參數(shù)類型注解。

      Sequence

      Sequence,是 collections.abc.Sequence 的泛型,在某些情況下,我們可能并不需要嚴(yán)格區(qū)分一個變量或參數(shù)到底是列表 list 類型還是元組 tuple 類型,我們可以使用一個更為泛化的類型,叫做 Sequence,其用法類似于 List,如:

      def?square(elements:?Sequence[float])?->?List[float]:

      return?[x?**?2?for?x?in?elements]

      NoReturn

      NoReturn,當(dāng)一個方法沒有返回結(jié)果時,為了注解它的返回類型,我們可以將其注解為 NoReturn,例如:

      def?hello()?->?NoReturn:

      print('hello')

      Any

      Any,是一種特殊的類型,它可以代表所有類型,靜態(tài)類型檢查器的所有類型都與 Any 類型兼容,所有的無參數(shù)類型注解和返回類型注解的都會默認(rèn)使用 Any 類型,也就是說,下面兩個方法的聲明是完全等價的:

      def?add(a):

      return?a?+?1

      def?add(a:?Any)?->?Any:

      return?a?+?1

      原理類似于 object,所有的類型都是 object 的子類。但如果我們將參數(shù)聲明為 object 類型,靜態(tài)參數(shù)類型檢查便會拋出錯誤,而 Any 則不會,具體可以參考官方文檔的說明:https://docs.python.org/zh-cn/3/library/typing.html?highlight=typing#the-any-type。

      TypeVar

      TypeVar,我們可以借助它來自定義兼容特定類型的變量,比如有的變量聲明為 int、float、None 都是符合要求的,實際就是代表任意的數(shù)字或者空內(nèi)容都可以,其他的類型則不可以,比如列表 list、字典 dict 等等,像這樣的情況,我們可以使用 TypeVar 來表示。

      例如一個人的身高,便可以使用 int 或 float 或 None 來表示,但不能用 dict 來表示,所以可以這么聲明:

      height?=?1.75

      Height?=?TypeVar('Height',?int,?float,?None)

      def?get_height()?->?Height:

      return?height

      這里我們使用 TypeVar 聲明了一個 Height 類型,然后將其用于注解方法的返回結(jié)果。

      NewType

      NewType,我們可以借助于它來聲明一些具有特殊含義的類型,例如像 Tuple 的例子一樣,我們需要將它表示為 Person,即一個人的含義,但但從表面上聲明為 Tuple 并不直觀,所以我們可以使用 NewType 為其聲明一個類型,如:

      Person?=?NewType('Person',?Tuple[str,?int,?float])

      person?=?Person(('Mike',?22,?1.75))

      這里實際上 person 就是一個 tuple 類型,我們可以對其像 tuple 一樣正常操作。

      Callable

      Callable,可調(diào)用類型,它通常用來注解一個方法,比如我們剛才聲明了一個 add 方法,它就是一個 Callable 類型:

      print(Callable,?type(add),?isinstance(add,?Callable))

      運行結(jié)果:

      typing.Callable??True

      在這里雖然二者 add 利用 type 方法得到的結(jié)果是 function,但實際上利用 isinstance 方法判斷確實是 True。

      Callable 在聲明的時候需要使用?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

      這里首先聲明了一個方法 date,接收三個 int 參數(shù),返回一個 str 結(jié)果,get_date_fn 方法返回了這個方法本身,它的返回值類型就可以標(biāo)記為 Callable,中括號內(nèi)分別標(biāo)記了返回的方法的參數(shù)類型和返回值類型。

      Union

      Union,聯(lián)合類型,Union[X, Y]?代表要么是 X 類型,要么是 Y 類型。

      聯(lián)合類型的聯(lián)合類型等價于展平后的類型:

      Union[Union[int,?str],?float]?==?Union[int,?str,?float]

      僅有一個參數(shù)的聯(lián)合類型會坍縮成參數(shù)自身,比如:

      Union[int]?==?int

      多余的參數(shù)會被跳過,比如:

      Union[int,?str,?int]?==?Union[int,?str]

      在比較聯(lián)合類型的時候,參數(shù)順序會被忽略,比如:

      Union[int,?str]?==?Union[str,?int]

      這個在一些方法參數(shù)聲明的時候比較有用,比如一個方法,要么傳一個字符串表示的方法名,要么直接把方法傳過來:

      def?process(fn:?Union[str,?Callable]):

      if?isinstance(fn,?str):

      #?str2fn?and?process

      pass

      elif?isinstance(fn,?Callable):

      fn()

      這樣的聲明在一些類庫方法定義的時候十分常見。

      Optional

      Optional,意思是說這個參數(shù)可以為空或已經(jīng)聲明的類型,即?Optional[X]?等價于?Union[X, None]。

      但值得注意的是,這個并不等價于可選參數(shù),當(dāng)它作為參數(shù)類型注解的時候,不代表這個參數(shù)可以不傳遞了,而是說這個參數(shù)可以傳為 None。

      如當(dāng)一個方法執(zhí)行結(jié)果,如果執(zhí)行完畢就不返回錯誤信息, 如果發(fā)生問題就返回錯誤信息,則可以這么聲明:

      def?judge(result:?bool)?->?Optional[str]:

      if?result:?return?'Error?Occurred'

      Generator

      如果想代表一個生成器類型,可以使用 Generator,它的聲明比較特殊,其后的中括號緊跟著三個參數(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

      案例實戰(zhàn)

      接下來讓我們看一個實際的項目,看看經(jīng)常用到的類型一般是怎么使用的。

      這里我們看的庫是 requests-html,是由 Kenneth Reitz 所開發(fā)的,其 GitHub 地址為:https://github.com/psf/requests-html,下面我們主要看看它的源代碼中一些類型是如何聲明的。

      這個庫的源代碼其實就一個文件,那就是 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 對象的列表,要么是單個 Element 對象,_Result?則要么是 Result 對象的列表,要么是單個 Result 對象。另外?_Attrs?其實就是字典類型,這里用 MutableMapping 來表示了,沒有用 Dict,也沒有用 Mapping。

      接下來再看一個 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?"".format(repr(self.element.tag),?'?'.join(attrs))

      @property

      def?attrs(self)?->?_Attrs:

      """Returns?a?dictionary?of?the?attributes?of?the?:class:`Element?`

      (`learn?more?`_).

      """

      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:

      使用類型注解讓 Python 代碼更易讀

      self._attrs[attr]?=?tuple(self._attrs[attr].split())

      return?self._attrs

      這里?__init__?方法接收非常多的參數(shù),同時使用?_URL?、_DefaultEncoding?進(jìn)行了參數(shù)類型注解,另外 attrs 方法使用了?_Attrs?進(jìn)行了返回結(jié)果類型注解。

      整體看下來,每個參數(shù)的類型、返回值都進(jìn)行了清晰地注解,代碼可讀性大大提高。

      以上便是類型注解和 typing 模塊的詳細(xì)介紹。

      本節(jié)代碼可以在公眾號「進(jìn)擊的Coder」回復(fù)「類型注解」獲取。

      Python

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:excel中怎么進(jìn)行自動換行
      下一篇:表格中如何輸入平米(怎么在表格里輸入平米)
      相關(guān)文章
      亚洲中文字幕久久精品无码APP| 亚洲人成影院在线无码观看| 亚洲国产中文字幕在线观看| 亚洲乱码av中文一区二区| 亚洲精品福利你懂| 久久精品国产亚洲av水果派| 亚洲成a人片在线观看无码专区| 精品国产日韩亚洲一区| 亚洲午夜无码AV毛片久久| 亚洲成网777777国产精品| 亚洲AV中文无码乱人伦| 亚洲AⅤ优女AV综合久久久| 国产亚洲精品2021自在线| 国产亚洲精彩视频| 国产亚洲精品美女| 亚洲精品无码AV中文字幕电影网站| 亚洲av无码不卡私人影院| 亚洲成AⅤ人影院在线观看| 亚洲精品视频免费观看| 国产精品亚洲mnbav网站| 亚洲精品色午夜无码专区日韩| 亚洲色婷婷六月亚洲婷婷6月| 亚洲精品无码国产| 亚洲VA中文字幕不卡无码| 亚洲国产精品国自产电影| 97久久精品亚洲中文字幕无码 | 亚洲精品中文字幕无乱码| 亚洲福利一区二区| 亚洲专区一路线二| 亚洲一区二区三区国产精华液| 亚洲国产无线乱码在线观看| 国产精品亚洲一区二区三区在线观看| 婷婷亚洲天堂影院| 国产亚洲自拍一区| 久久久久亚洲AV成人无码网站 | 亚洲偷自拍另类图片二区| 国产精品手机在线亚洲| 亚洲综合亚洲综合网成人| 国产啪亚洲国产精品无码| 亚洲国产另类久久久精品小说| 西西人体44rt高清亚洲|