<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          python怎么做類型標(biāo)注

          共 8339字,需瀏覽 17分鐘

           ·

          2021-09-24 20:09

          盡管pyhton從3.5版本開始就引入了類型系統(tǒng),但到目前為止,接受程度不是特別的高,很多的開源庫仍然沒有使用類型標(biāo)注。

          究其原因,我認(rèn)為最主要的一條是類型標(biāo)注不是必須的,且python解釋器并不檢查你所做的類型標(biāo)注,那么大家在代碼里添加類型標(biāo)注的動力也就不大。

          一項(xiàng)好的技術(shù),沒有被推廣,單純的說它不是必須的,不能解釋所有的問題,另一個影響類型標(biāo)注廣泛使用的原因,我認(rèn)為是類型標(biāo)注有一點(diǎn)難度,官方文檔沒有盡全力為大家解釋該如何使用。這使得很多想要使用類型標(biāo)注的人望而卻步,畢竟,即便費(fèi)力的做了,也沒有很明顯很直觀的收益。

          本文將通過實(shí)際的例子為你展示如何在python代碼里做類型標(biāo)注。

          1. 為變量做類型標(biāo)注

          我們先來通過最簡單的情況,為變量做類型標(biāo)注,變量可以有如下類型:

          1. int

          2. float

          3. bool

          4. str

          5. bytes

          6. None

          7. list

          8. tuple

          9. set

          10. dict

          1.1 簡單的數(shù)據(jù)類型

          int, float, bool, str, None,bytes 這些都是最簡單的數(shù)據(jù)類型,他們的類型標(biāo)注也是最簡單的,創(chuàng)建腳本typehint.py

          from typing import Optional, Union, Any

          a: int = 8
          b: bool = True
          c: str = 'ok'
          d: None = None
          e: float = 9.8
          f: bytes = b'32'

          使用mypy對類型標(biāo)注進(jìn)行檢查

          mypy typehint.py

          檢查結(jié)果

          Success: no issues found in 1 source file

          這說明我們對這4種變量的類型標(biāo)注是正確的,但上面的代碼存在嚴(yán)重的缺陷,變量d我為它標(biāo)注為None,那么d這個變量就永遠(yuǎn)只能為None了,如果我將其賦值為其他類型,類型標(biāo)注檢查就會報錯, 修改代碼

          from typing import Optional, Union, Any

          a: int = 8
          b: bool = True
          c: str = 'ok'
          d: None = None
          e: float = 9.8
          f: bytes = b'32'

          d = 5

          使用mypy檢查結(jié)果

          typehint.py:9: error: Incompatible types in assignment (expression has type "int", variable has typ
          e "None")
          Found 1 error in 1 file (checked 1 source file)

          修改后的代碼可以正常執(zhí)行,因?yàn)閜ython解釋器才不管類型標(biāo)注呢,但是將5賦值給d就不符合類型標(biāo)注的要求了。

          類型標(biāo)注的意義是標(biāo)注一個變量的數(shù)據(jù)類型,此后的代碼都應(yīng)當(dāng)遵守對這個變量的類型標(biāo)注,這就要求我們,不能隨意的修改變量的數(shù)據(jù)類型。

          1.2 使用 Optional

          在1.1 的例子中,d變量別標(biāo)注為None類型,可一個變量始終賦值為None是毫無意義的事情,你只是在最初的時候不想給它一個明確的值才賦值為None的,后面的代碼一定會修改變量d的值的。

          假設(shè)你對變量d的使用是希望為它賦值一個int類型的數(shù)據(jù),那么在類型標(biāo)注的時候,就應(yīng)當(dāng)做好準(zhǔn)備

          from typing import Optional, Union, Any

          a: int = 8
          b: bool = True
          c: str = 'ok'
          d: Optional[int] = None
          e: float = 9.8
          f: bytes = b'32'

          d = 5

          Optional表示可選,那么d就可以被賦值成int類型,此外也可以是None。

          1.3 使用Union

          d 能賦值成int,也可能被賦值成float, 這種情況,要結(jié)合Optional 和 Union

          from typing import Optional, Union, Any

          a: int = 8
          b: bool = True
          c: str = 'ok'
          d: Optional[Union[int, float]] = None
          e: float = 9.8
          f: bytes = b'32'

          d = 5
          d = 9.8
          d = None

          Union表示或的意思,d變量的類型,可以是None,也可以是int或者float。

          接下來,你可能會問,可不可以將a變量的類型標(biāo)注設(shè)置為Union[int, float], 讓a以賦值成int也可以賦值成為float?從純粹的技術(shù)實(shí)現(xiàn)上講這樣做沒有問題

          from typing import Optional, Union, Any

          a: Union[int, float] = 8 # 堅(jiān)決反對你這樣做
          b: bool = True
          c: str = 'ok'
          d: Optional[Union[int, float]] = None
          e: float = 9.8
          f: bytes = b'32'

          d = 5
          d = 9.8
          d = None

          a = 8.9

          但從工程實(shí)踐的角度來看,這種做法簡直就是脫褲子放屁,多此一舉。我們?yōu)樽兞窟M(jìn)行類型標(biāo)注的目的就是為了防止變量在使用過程中由于缺乏類型檢查導(dǎo)致類型變來變?nèi)ィ氵@樣不就是又回到了之前的狀態(tài)了么,那做類型標(biāo)注還有什么意義呢,還不如不做。

          d變量與其他幾個變量不同,d變量初始值賦值為None,我們心里很清楚,它的值一定會被改變的,不然留著它毫無意義, 而一旦改變,就必然導(dǎo)致數(shù)據(jù)類型發(fā)生變化,因此才需要我們使用Optional。其他變量呢,值改變了,數(shù)據(jù)類型可以不發(fā)生變化,如果類型發(fā)生了變化,說明你的操作就違背了類型標(biāo)注的初衷。

          1.4 為容器類型做標(biāo)注

          list, tuple, dict, set, 為這4個容器類型數(shù)據(jù)做標(biāo)注,要稍微麻煩一點(diǎn)點(diǎn), 先來看最簡單的set,

          1.4.1 為集合做標(biāo)注

          在使用set時,我們默認(rèn)只會向集合中添加相同數(shù)據(jù)類型的值,但你要明確一點(diǎn),集合可以存儲不同類型的數(shù)據(jù)。

          from typing import Optional, Union, Any, Set

          s: Set[int] = {1, 2, 3}

          這段代碼可以通過mypy的檢查, 接下來看列表如何做標(biāo)注

          1.4.2 為列表做標(biāo)注

          from typing import Optional, Union, Any, Set, List, Tuple

          s: Set[int] = {1, 2, 3}
          l: List[int] = [1, 2, 3]

          列表標(biāo)注的方式與集合是一樣的,但我們都清楚,列表里存儲的數(shù)據(jù)往往都是類型不相同的,比如下面的列表

           [1, 2, 3, 'a', 'b', True]

          對這種情況,就需要使用1.3小節(jié)所介紹的Union

          from typing import Optional, Union, Any, Set, List, Tuple

          s: Set[int] = {1, 2, 3}
          l: List[Union[int, str, bool]] = [1, 2, 3, 'a', 'b', True]

          1.4.3 為元組做標(biāo)注

          為元組做標(biāo)注,不能使用和列表相同的辦法,而是要逐個索引位置進(jìn)行標(biāo)注

          from typing import Optional, Union, Any, Set, List, Tuple

          t: Tuple[int, str, bool] = (3, 'ok', True)

          1.4.4 為字典做標(biāo)注

          先來看最簡單的,字典的key都是字符串,value都是int

          from typing import Optional, Union, Any, Set, List, Tuple, Dict

          d: Dict[str, int] = {'ok': 4}

          這是最理想的情況,但實(shí)際情況往往更復(fù)雜,字典的key可以有str類型,也可以有int類型,當(dāng)類型不確定的時候,我們就可以使用Union

          from typing import Optional, Union, Any, Set, List, Tuple, Dict

          d: Dict[str, int] = {'ok': 4}
          d1: Dict[Union[str, int], Union[str, int, float]] = {'ok': 4, 3: 'ok', 4: 3.2}

          還有更復(fù)雜的情況

          from typing import Optional, Union, Any, Set, List, Tuple, Dict

          dic: Dict[str, Union[Tuple[int, int], Dict[int, int]]] = {
          'ok': (1, 2),
          'dic': {5: 9}
          }

          字典里的value,可以是元組,也可以是字典,字典嵌套了字典,在做類型標(biāo)注的時候,也就需要以嵌套的形式進(jìn)行標(biāo)注。對于這種復(fù)雜的字典,我的建議就是簡化處理

          from typing import Optional, Union, Any, Set, List, Tuple, Dict

          dic: Dict[str, Union[Tuple, Dict]] = {
          'ok': (1, 2),
          'dic': {5: 9}
          }

          value可以是元組,也可以是字典,我只要標(biāo)注到這個程度就可以了,不再繼續(xù)詳細(xì)的進(jìn)行標(biāo)注,不然單單一個類型標(biāo)注就把代碼搞的難以理解了。

          1.4.5 容器類型標(biāo)注總結(jié)

          容器類型標(biāo)注,可以粗略的進(jìn)行標(biāo)注,也可以詳細(xì)的進(jìn)行標(biāo)注,這完全取決于你的想法,我的觀點(diǎn)是,在不影響代碼可閱讀性的前提下詳細(xì)標(biāo)注,反之則粗略標(biāo)注

          from typing import Optional, Union, Any, Set, List, Tuple, Dict

          l: List = [1, 2, ['2', '3']] # 粗略標(biāo)注
          l2 : List[Union[int, List[str]]] = [1, 2, ['2', '3']] # 詳細(xì)標(biāo)注

          2. 為函數(shù)做標(biāo)注類型

          2.1 對形參和返回值進(jìn)行標(biāo)注

          為函數(shù)做標(biāo)注類型,需要對每一個形參做類型標(biāo)注,同時還要對函數(shù)的返回值做類型標(biāo)注

          def add(x: int, y: int) -> int:
          return x + y

          print(add(2, 5))

          形參的變量類型,我們事先是清楚的,因此你只需要按照第一節(jié)里的講解對形參進(jìn)行標(biāo)注就可以了,函數(shù)的返回值在函數(shù)定義時進(jìn)行標(biāo)注,在有括號后面緊跟著進(jìn)行標(biāo)注,注意需要用到“->”。

          如果返回值的類型可能是int,也可能是None,該怎么標(biāo)注呢?其實(shí)這種情況完全可以參考對變量的標(biāo)注

          from typing import Optional


          def add(x: Optional[int], y: int) -> Optional[int]:
          if not isinstance(x, int):
          return None
          return x + y

          add(3, 4)
          add(None, 4)

          看到這里你應(yīng)該明白,對函數(shù)參數(shù)及返回值的標(biāo)注,完全遵守對變量的標(biāo)注規(guī)則,唯一需要區(qū)別對待的是函數(shù)的返回值。

          2.2 對可變參數(shù)進(jìn)行標(biāo)注

          python的可變參數(shù)一個是*args, 一個是**kwargs,從函數(shù)的視角來看,args的類型是元組,kwargs的類型是字典,先來看args

          def add(*args: int) ->int:
          sum_value = sum(args)
          return sum_value

          print(add(1, 2, 3))

          我很確定args里的元素都是int類型,那么直接標(biāo)注為int就可以了,如果還有其他類型,那么就需要使用Union

          from typing import Optional, Union


          def add(*args: Union[str, int, float]) -> float:
          sum_value = sum([float(item) for item in args])
          return sum_value

          print(add(1, '2', 3.8))

          傳入的可變參數(shù)可以是str,int,float中的任意一個,args雖然是元組,但是我們不是按照元組來進(jìn)行標(biāo)注,標(biāo)注的是對這些參數(shù)的期望值,再來看**kwargs

          from typing import Any, Union


          def add(**kwargs: Union[int, str, float]) -> None:
          print(kwargs)

          dic = {
          'a': 3,
          'b': '5',
          'c': 9.3
          }

          add(**dic)
          add(a=3, b='5', c=9.3)

          關(guān)鍵字參數(shù)的值,有int,str,float三個類型,我們要標(biāo)注的是這些參數(shù)的值,而不是字典。

          2.3 callable對象做參數(shù)

          在python中,函數(shù)也是對象,也可以作為函數(shù)的參數(shù)

          from typing import Callable, Any, Union
          import time
          from functools import wraps


          def cost(func: Callable):
          @wraps(func)
          def warpper(*args: Any, **kwargs: Any):
          t1 = time.time()
          res = func(*args, **kwargs)
          t2 = time.time()
          print(func.__name__ + "執(zhí)行耗時" + str(t2-t1))
          return res
          return warpper

          @cost
          def test(sleep_time: Union[float, int]) -> None:
          """
          測試裝飾器
          :param sleep_time:
          :return:
          """

          time.sleep(sleep_time)


          test(1)

          當(dāng)形參是函數(shù)對象時,使用Callable進(jìn)行標(biāo)注。

          3. 標(biāo)注自定義類

          3.1 自定義類實(shí)例

          在程序里自定義了一個類,對于這個類的實(shí)例,我們也可以標(biāo)注

          class Demo():
          pass

          d : Demo = Demo()

          def test(demo: Demo):
          pass

          test(d)

          3.2 標(biāo)注類屬性

          類屬性可以使用ClassVar進(jìn)行標(biāo)注,標(biāo)注后,如果實(shí)例嘗試修改類屬性,mypy在檢查時會報錯,但python解釋器可以正常執(zhí)行程序,原因前面已經(jīng)強(qiáng)調(diào)過,解釋器不受類型標(biāo)注影響

          from typing import ClassVar

          class Demo():
          count: ClassVar[int] = 0

          d: Demo = Demo()

          print(d.count)
          d.count = 20 # mypy 檢查會報錯

          4. 不常見的類型標(biāo)注

          有些對象的類型不如基礎(chǔ)數(shù)據(jù)類型那樣常見,我這里做一個總結(jié)并一一舉例說明

          4.1 迭代器

          from typing import Iterator

          def my_generator(n: int) -> Iterator:
          index = 0
          while index < n:
          yield index
          index += 1

          generate = my_generator(5)

          my_generator是生成器函數(shù),它的返回值是一個generator類型的對象,是一個迭代器,的返回值就可以標(biāo)注為Iterator

          4.2 字典的items(), keys(), values()返回值

          字典的items(),keys(),values()三個方法分別返回字典的key-value對,所有的key和所有的values,標(biāo)記他們類型的方法如下:

          from typing import ItemsView, KeysView, ValuesView


          def test_1() -> ItemsView:
          dic = {'name': 'python'}
          return dic.items()


          def test_2() ->KeysView:
          dic = {'name': 'python'}
          return dic.keys()


          def test_3() ->ValuesView:
          dic = {'name': 'python'}
          return dic.values()

          4.3 Sequence

          Sequence 可以用來標(biāo)記任何序列對象,比如列表,元素,字符串,字節(jié)串,他們都是序列,如果你對變量的類型不是很確定,但可以肯定它一定是一個序列,那么就可以使用Sequence

          from typing import Sequence, List


          lst: Sequence[int] = []
          name: Sequence = 'python'
          tup: Sequence = (1, 2, 4.5)
          bstring: Sequence = b'sds'

          5. 泛型和TypeVar工廠函數(shù)

          泛型和TypeVar工廠函數(shù),都是為了更方便的進(jìn)行類型標(biāo)注而存在的。假設(shè)你現(xiàn)在要定義一個棧,Stack類,你需要一個列表來存儲數(shù)據(jù),此時,你會遇到一個難處,如果這個棧只允許int類型數(shù)據(jù)入棧,那么你就只能這樣定義

          from typing import List

          class Stack():
          def __init__(self):
          self.data: List[int] = []

          但如果這個棧只允許float類型的數(shù)據(jù)入棧,你就只能這樣來定義

          class Stack():
          def __init__(self):
          self.data: List[float] = []

          這樣就有點(diǎn)犯難了,兩個存儲不同數(shù)據(jù)類型的棧就需要兩個定義,但這兩個類的代碼是完全一致的,只是類型標(biāo)注不同,有沒有什么辦法,可以用一套代碼實(shí)現(xiàn)不同類型的標(biāo)注呢?

          這就要用到泛型和TypeVar函數(shù)

          from typing import TypeVar, Generic, List


          T = TypeVar('T')


          class Stack(Generic[T]):
          def __init__(self):
          self.data: List[T] = []

          def push(self, item: T):
          self.data.append(item)

          def pop(self) -> T:
          return self.data.pop(-1)

          def top(self) -> T:
          return self.data[-1]

          def size(self) -> int:
          return len(self.data)

          def is_empty(self) -> bool:
          return len(self.data) == 0

          stack = Stack[int]()
          stack.push(3)
          stack.push(5)
          print(stack.pop())

          我定義一個泛型,所謂泛型,就是先不明確它的類型,那么什么時候明確它的類型呢,等到實(shí)際調(diào)用的時候,比如

          stack = Stack[int]()

          我在創(chuàng)建stack對象時來確定泛型T的數(shù)據(jù)類型,如果你希望棧只存儲float類型數(shù)據(jù),你就可以這樣來寫

          stack = Stack[float]()

          使用泛型,相當(dāng)于創(chuàng)建了一個模板,在調(diào)用模板前,來確定泛型的數(shù)據(jù)類型,一套代碼,實(shí)現(xiàn)了多套數(shù)據(jù)類型標(biāo)注,豈不美哉。


          瀏覽 90
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  爱液 视频网站 | 五月丁香婷中文 | 狠狠干网| 激情偷乱人成视频在线观看 | 高清无码免费视频在线观看 |