python之聲明函數時指定傳入參數的數據類型 || 函數return返回值的數據類型(函數參數的注釋以及函數返回值的注釋)|| python之內置typing模塊:類型提示支持


前言:

①在 Python 3.5 中,Python PEP 484 引入了類型注解(type hints),在 Python 3.6 中,PEP 526 又進一步引入了變量注解(Variable Annotations)。

②具體的變量注解語法可以歸納為兩點:

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

③在PEP 8 中,具體的格式是這樣規定的:

  • 在聲明變量類型時,變量后方緊跟一個冒號,冒號后面跟一個空格,再跟上變量的類型。
  • 在聲明方法返回值的時候,箭頭左邊是方法定義,箭頭右邊是返回值的類型,箭頭左右兩邊都要留有空格。

④值得注意的是,這種類型和變量注解實際上只是一種類型提示,對運行實際上是沒有影響的。

⑤另外也有一些庫是支持類型檢查的,比如 mypy,安裝之后,利用 mypy 即可檢查出 Python 腳本中不符合類型注解的調用情況。

注解表達式

當你自己寫的函數或方法,要被其他人調用時,如果你想讓對方知道函數或方法傳入參數的數據類型,可以這樣定義:

def demo(name: str, age: 'int > 0' = 20) -> str:  # ->str 表示該函數的返回值是str類型的
    print(name, type(name))
    print(age, type(age))
    return "hello world"


if __name__ == '__main__':
    demo(1, 2)  # 這里的參數1會顯示黃色, 但是可以運行不會報錯
    demo('小小', 2)  # 正常顯示

運行結果:

<class 'int'> # 1 
<class 'int'> # 2 

<class 'str'> # 小小 
<class 'int'> # 2 

總結:

①以上是注解表達式的應用方法,注解中最常用的就是類( str  或  int )類型和字符串(如  'int>0' )。

②對於注解python不會做任何處理,它只是存儲在函數的 __annotations__ 屬性(字典)中 【其中包括函數入參參數的注解以及函數 return 返回的值的注解

對於注解,python不做檢查, 不做強制,,不做驗證, 什么操作都不做。

④換而言之,,注釋對python解釋器沒有任何意義, 只是為了方便使用函數的人。

指定傳入參數的數據類型為any

若聲明某函數時指定函數傳入參數的數據類型為any,則調用該函數時該參數的參數類型可以為任意類型。

代碼如下:

def demo(name: any, age: 'int > 0' = 20) -> str:  # ->str 表示該函數的返回值是str類型的
    print(name, type(name))
    print(age, type(age))
    return "hello world"


if __name__ == '__main__':
    demo(name=1, age=2)  # 正常顯示
    demo(name='小小', age=2)  # 正常顯示

運行結果:

函數參數注解

代碼如下:

def demo(name: str, age: 'int > 0' = 20) -> str:  # ->str 表示該函數的返回值是str類型的
    print(name, type(name))
    print(age, type(age))
    return "hello world"


if __name__ == '__main__':
    print(demo.__annotations__)

解釋:demo函數的參數注解存放在 __annotations__ 字典中。

運行結果:

{'name': <class 'str'>, 'age': 'int > 0', 'return': <class 'str'>}

typing:強類型聲明

1、typing介紹

Python是一門弱類型的語言,很多時候我們可能不清楚函數參數的類型或者返回值的類型,這樣會導致我們在寫完代碼一段時間后回過頭再看代碼,忘記了自己寫的函數需要傳什么類型的參數,返回什么類型的結果,這樣就不得不去閱讀代碼的具體內容,降低了閱讀的速度, typing 模塊可以很好的解決這個問題。

【注意】:  typing 模塊只有在python3.5以上的版本中才可以使用,pycharm目前支持 typing 檢查。

2、typing的作用

  • 類型檢查,防止運行時出現參數和返回值類型不符合。
  • 作為開發文檔附加說明,方便使用者調用時傳入和返回參數類型。
  • 該模塊加入后並不會影響程序的運行,不會報正式的錯誤,只有提醒pycharm目前支持typing檢查,參數類型錯誤會黃色提示

3、常用數據類型

  • int,long,float: 整型,長整形,浮點型;
  • bool,str: 布爾型,字符串類型;
  • List,Tuple,Dict,Set:列表,元組,字典, 集合;
  • Iterable,Iterator:可迭代類型,迭代器類型;
  • Generator:生成器類型;

除了以上常用的類型,還支持 Any , Union , Tuple , Callable , TypeVar 和 Generic 類型組成。有關完整的規范,請參閱 PEP 484 。有關類型提示的簡單介紹,請參閱 PEP 483

4、代碼示例

func函數要求傳入的第2個參數為 str 類型,而我們調用時傳入的參數是 int 類型,此時Pycharm就會用黃色來警告你,我們將光標放到黃色的地方,會出現下面的提示:

寫着期望類型是 str ,而現在是 int ,但是 typing的作用僅僅是提示,並不會影響代碼執行;

執行結果如下:我們會發現並沒有報錯,因為 typing僅僅是起到了提醒的作用。

[2, 3]

5、類型別名

類型別名,就是給復雜的類型取個別名

# 給List[float]類型取個別名為Vector
Vector = List[float]


def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

new_vector = scale(2.0, [1.0, -4.2, 5.4])

當然,類型別名我們完全可以不用,用以下寫法也一樣,看個人喜好

def scale(scalar: float, vector: List[float]) -> List[float]:
    return [scalar * num for num in vector]

6、typing 模塊中包含的數據類型

AbstractSet = typing.AbstractSet
Any = typing.Any
AnyStr = ~AnyStr
AsyncContextManager = typing.AbstractAsyncContextManager
AsyncGenerator = typing.AsyncGenerator
AsyncIterable = typing.AsyncIterable
AsyncIterator = typing.AsyncIterator
Awaitable = typing.Awaitable
ByteString = typing.ByteString
Callable = typing.Callable
ClassVar = typing.ClassVar
Collection = typing.Collection
Container = typing.Container
ContextManager = typing.AbstractContextManager
Coroutine = typing.Coroutine
Counter = typing.Counter
DefaultDict = typing.DefaultDict
Deque = typing.Deque
Dict = typing.Dict
FrozenSet = typing.FrozenSet
Generator = typing.Generator
Hashable = typing.Hashable
ItemsView = typing.ItemsView
Iterable = typing.Iterable
Iterator = typing.Iterator
KeysView = typing.KeysView
List = typing.List
Mapping = typing.Mapping
MappingView = typing.MappingView
MutableMapping = typing.MutableMapping
MutableSequence = typing.MutableSequence
MutableSet = typing.MutableSet
NoReturn = typing.NoReturn
Optional = typing.Optional
Reversible = typing.Reversible
Sequence = typing.Sequence
Set = typing.Set
Sized = typing.Sized
TYPE_CHECKING = False
Tuple = typing.Tuple
Type = typing.Type
Union = typing.Union
ValuesView = typing.ValuesView

前言

①上述案例只是用一個簡單的 int 類型/str類型 實例,下面再來看下一些相對復雜的數據結構,例如列表、元組、字典等類型怎么樣來聲明。

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

names: list = ['Germey', 'Guido']
version: tuple = (3, 7, 4)
operations: dict = {'show': False, 'sort': True}

這么看上去沒有問題,確實聲明為了對應的類型,但實際上並不能反映整個列表、元組的結構;

比如我們只通過類型注解是不知道 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}

這樣一來,變量的類型便可以非常直觀地體現出來。

④目前 typing 模塊也已經被加入到  Python 標准庫中,不需要安裝第三方模塊就可以直接使用。

typing模塊的具體用法

在引入的時候就直接通過  typing  模塊引入

例如:

from typing import List, Tuple

List

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

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

另外還可以嵌套聲明

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

【注意】因為容器中的元素的類型信息由於泛型不同,通過一般方式靜態推斷,因此抽象類被用來拓展表示容器中的元素。

Tuple、NamedTuple

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

比如想聲明一個元組,分別代表姓名、年齡、身高,三個數據類型分別為 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 的泛型

根據官方文檔,Dict 推薦用於注解返回類型,Mapping 推薦用於注解參數。它們的使用方法都是一樣的,其后跟一個中括號,中括號內分別聲明鍵名、鍵值的類型,如:

def size(rect: Mapping[str, int]) -> Dict[str, int]:
 return {'width': rect['width'] + 100, 'height': rect['width'] + 100}

這里將 Dict 用作了返回值類型注解,將 Mapping 用作了參數類型注解。

MutableMapping 則是 Mapping 對象的子類,在很多庫中也經常用 MutableMapping 來代替 Mapping。

Set、AbstractSet

Set,集合,是 set 的泛型

AbstractSet,是 collections.abc.Set 的泛型

根據官方文檔,Set 推薦用於注解返回類型,AbstractSet 用於注解參數。它們的使用方法都是一樣的,其后跟一個中括號,里面聲明集合中元素的類型,如:

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

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

Sequence

Sequence,是 collections.abc.Sequence 的泛型

在某些情況下,我們可能並不需要嚴格區分一個變量或參數到底是列表 list 類型還是元組 tuple 類型,我們可以使用一個更為泛化的類型,叫做 Sequence,其用法類似於 List,如:

def square(elements: Sequence[float]) -> List[float]:
 return [x ** 2 for x in elements]

NoReturn

NoReturn,當一個方法沒有返回值時,為了注解它的返回類型,我們可以將其注解為 NoReturn,例如:

def hello() -> NoReturn:
 print('hello')

Any

Any,是一種特殊的類型,它可以代表所有類型靜態類型檢查器的所有類型都與 Any 類型兼容;

所有的無參數類型注解無返回類型注解的都會默認使用 Any 類型。

如,下面兩個方法的聲明是完全等價的:

def add(a):
 return a + 1

def add(a: Any) -> Any:
 return a + 1

原理類似於 object,所有的類型都是 object 的子類。

但如果我們將參數聲明為 object 類型,靜態參數類型檢查便會拋出錯誤,而 Any 則不會,具體可以參考官方文檔的說明:https://docs.python.org/zh-cn/3/library/typing.html?highlight=typing#the-any-type

TypeVar

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

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

height = 1.75
Height = TypeVar('Height', int, float, None)
def get_height() -> Height:
 return height

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

NewType

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

Person = NewType('Person', Tuple[str, int, float])
person = Person(('Mike', 22, 1.75))

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

①①Callable

Callable,可調用類型,它通常用來注解一個方法,比如下面的 add 方法,它就是一個 Callable 類型:

from typing import Callable


def add(a):
    return a + 1


print(Callable, type(add), isinstance(add, Callable), sep='\n')

運行結果:

typing.Callable
<class 'function'>
True

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

Callable 在聲明的時候需要使用 Callable[[Arg1Type, Arg2Type, ...], ReturnType] 這樣的類型注解,將參數類型和返回值類型都要注解出來,例如:

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 參數,返回一個 str 結果,get_date_fn 方法返回了這個方法本身,它的返回值類型就可以標記為 Callable,中括號內分別標記了返回的方法的參數類型和返回值類型。

①②Union

Union,聯合類型,Union[X, Y] 代表要么是 X 類型,要么是 Y 類型。

聯合類型的聯合類型等價於展平后的類型:

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

僅有一個參數的聯合類型會壓縮成參數自身,比如:

Union[int] == int

多余的參數會被跳過,比如:

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

在比較聯合類型的時候,參數順序會被忽略,比如:

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

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

def process(fn: Union[str, Callable]):
 if isinstance(fn, str):
  # str2fn and process
  pass
 elif isinstance(fn, Callable):
  fn()

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

①③Optional

Optional:意思是說這個參數可以為空已經聲明的類型,即 Optional[X] 等價於 Union[X, None]。

但值得注意的是,這個並不等價於可選參數,當它作為參數類型注解的時候,不代表這個參數可以不傳遞了,而是說這個參數可以傳為 None。

from typing import Optional


def judge(result: bool) -> Optional[str]:
    if result:
        return 'Error Occurred'


if __name__ == '__main__':
    print('當入參result=False時:', judge(result=False))
    print('當入參result=True時:', judge(result=True))

執行結果:

當入參result=False時: None  # 此時函數沒有返回值,但是在函數聲明時已經定義函數返回值為unit[str, None],所以當函數沒有返回值的情況下返回None
當入參result=True時: Error Occurred # 函數存在返回值的情況返回一個字符串

①④Generator

如果想代表一個生成器類型,可以使用 Generator,它的聲明比較特殊,其后的中括號緊跟着三個參數,分別代表 YieldType、SendType、ReturnType,如:

from typing import Generator


def echo_round() -> Generator[int, float, str]:
    sent = yield 0
    while sent >= 0:
        sent = yield round(sent)
    return 'Done'

在這里 yield 關鍵字后面緊跟的變量的類型就是 YieldType,yield 返回的結果的類型就是 SendType,最后生成器 return 的內容就是 ReturnType。

當然很多情況下,生成器往往只需要 yield 內容就夠了,我們是不需要 SendType 和 ReturnType 的,可以將其設置為空,如:

from typing import Generator


def infinite_stream(start: int) -> Generator[int, None, None]:
    while True:
        yield start
        start += 1

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM