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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!