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

 

posted @ 2021-10-11 14:33  习久性成  阅读(17218)  评论(0编辑  收藏  举报