Python 中如何编写类型提示

哈喽大家好,我是咸鱼

我们知道 Python 是一门具有动态特性的语言,在编写 Python 代码的时候不需要显式地指定变量的类型

这样做虽然方便,但是降低了代码的可阅读性,在后期 review 代码的时候容易对变量的类型产生混淆,需要查阅大量上下文,导致后期维护困难

为了提高代码的可读性、可维护性,Python 在 PEP 484 中引入了类型提示( type hinting)。类型提示是 Python 中一个可选但非常有用的功能,可以使代码更易于阅读和调试

关于类型提示的介绍可以看:

https://realpython.com/python-type-hints-multiple-types/#use-pythons-type-hints-for-one-piece-of-data-of-alternative-types

在编写函数的时候,我们通常指定其返回值是一种数据类型,但是在下面这些情况下可以指定返回不同类型的数据:

  • 当函数使用条件语句返回不同类型结果时
  • 函数有时返回值,有时不返回值
  • 当函数遇到错误时,可能需要返回与正常结果的返回类型不同的特定错误对象
  • 想要设计更灵活更通用的代码

那么这时候该如何编写类型提示呢?

为常规函数编写类型提示

def parse_email(email_address: str) -> str | None:if "@" in email_address:username, domain = email_address.split("@")return usernamereturn None

上面的函数中有一个条件判断语句,用于检查参数 email_address 电子邮箱地址里面是否包含 @ 符号。如果有,则返回用户名 username ,没有则返回 None,表示电子邮箱地址不完整

所以该函数的返回值要么是包含用户名的字符串,要么是 None。那么我们可以用管道符(|) 来表示函数返回单个值的可选类型

# 要么返回 str ,要么返回 None
str | None:

在 Python 3.10 之前,我们还可以使用 typing 模块中的 Union 来表示函数返回的是str 还是 None

from typing import Uniondef parse_email(email_address: str) -> Union[str, None]:if "@" in email_address:username, domain = email_address.split("@")return usernamereturn None

那如果单个返回值里面包含多个对象的话,该如何编写类型提示呢?

比如说上面的函数,我希望它:

  • 如果是有效的邮箱,则返回用户名和域名
  • 如果不是有效的邮箱,返回 None

PS: 当返回值里有多个对象时,默认是以元组的形式返回

所以我们可以这么写类型提示

def parse_email(email_address: str) -> tuple[str, str] | None:if "@" in email_address:username, domain = email_address.split("@")return username, domainreturn None

tuple[str, str]| None ,表示返回值可以是两个字符串的元组或None

如果使用 typing 模块中的 Union来编写类型提示的话,如下

from typing import Tuple, Uniondef parse_email(email_address: str) -> Union[Tuple[str, str], None]:if "@" in email_address:username, domain = email_address.split("@")return username, domainreturn None

举三反一一下,如果单个返回值包含三个对象,可以这么写

# 函数返回值里面包含了字符串、整数、布尔值
def get_user_info(user: User) -> tuple[str, int, bool]:...

为回调函数编写类型提示

在 Python 中,函数可以作为另一个函数的参数或者返回其他函数。这种函数被称为高阶函数

比如说 Python内置函数(例如sorted()map()filter())可以接受一个函数作为参数

这个作为参数传递的函数通常被称为回调函数(callback function),因为它在另一个函数中被调用(“回调”),回调函数是一种可调用对象(callable objects)

可调用对象指的是可以像函数一样调用的对象。Python 中可调用对象包括常规函数、lambda 表达式或实现了__call__()方法的类)

那么我们在调用回调函数的时候,该如何编写类型注释呢?

比如说下面的例子

>>> from collections.abc import Callable>>> def apply_func(
...     func: Callable[[str], tuple[str, str]], value: str
... ) -> tuple[str, str]:
...     return func(value)
...
>>> def parse_email(email_address: str) -> tuple[str, str]:
...     if "@" in email_address:
...         username, domain = email_address.split("@")
...         return username, domain
...     return "", ""
...
>>> apply_func(parse_email, "claudia@realpython.com")
('claudia', 'realpython.com')

在函数 apply_func 的类型提示中,将回调函数 func作为第一个参数,将字符串 value 作为第二个参数,返回值是一个包含两个 str 的 tuple

Callable[[str], tuple[str, str]]:表示回调函数 func 接收参数是一个 str,返回值是一个包含两个 str 的 tuple

在函数 parse_email 的类型提示中,接受一个 str 类型的参数 email_address ,返回值类型是一个包含两个 str 的 tuple

那如果我希望函数 apply_func 能够接收具有多种输入类型的不同函数作为参数(比如说回调函数有多个输入参数)并有多种返回类型,该怎么办?

我们可以用省略号... 来表示可调用对象(例如回调函数)可以接受多个参数,这样就不需要依次列出接受参数的类型

def apply_func( func: Callable[...,tuple[str, str]], value: str) -> tuple[str, str]return func(value)

或者使用 typing 模块中的类型来指定任何返回 Any 类型

from collections.abc import Callable
from typing import Anydef apply_func( func: Callable[...,Any], *args: Any, **kwargs: Any) -> tuple[str, str]return func(*args, **kwargs)

我们还可以在类型提示中把回调函数的返回值类型写成 T ,这是一个类型变量type variable,可以代表任何类型

from collections.abc import Callable
from typing import Any, TypeVarT = TypeVar("T")def apply_func(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:return func(*args, **kwargs)

apply_func 的返回值类型也是 T,*args: Any, **kwargs: Any 表示 apply_func 可以接受任意数量的参数(包括 0)

为生成器编写类型提示

在 Python 中,生成器(Generators)是一种特殊的迭代器,它们允许按需生成值,而无需提前生成所有值并将其存储在内存中

生成器逐个产生并返回值,这对于处理大量数据或无限序列非常有用

生成器可以通过函数与 yield 语句创建。yield 语句在生成器函数内部被用来产生一个值,并在暂停生成器的同时返回该值给调用者

每次调用生成器的 next()方法或使用 for循环时,生成器函数会从上一次yield语句的位置恢复执行,并继续执行到下一个yield语句或函数结束

继续上面的例子,我现在有大量的邮箱需要判断是否有效,与其将每个解析的结果存储在内存中并让函数一次返回所有内容,不如使用生成器一次生成一个解析结果

>>> from collections.abc import Generator>>> def parse_email() -> Generator[tuple[str, str], str, str]:# 定义初始的 sent 值为元组 ("", "")
...     sent = yield ("", "")
...     while sent != "":
...         if "@" in sent:
...             username, domain = sent.split("@")
...             sent = yield username, domain
...         else:
...             sent = yield "invalid email"
...     return "Done"

Generator[tuple[str, str], str, str]类型提示里面有三个参数(后面两个是可选的),其中:

  • yield 类型:第一个参数是生成器生成的结果。例子中它是一个元组,包含两个字符串,一个表示用户名,另一个表示域名
  • send 类型:第二个参数表示使用 send 方法发送给生成器的内容。例子中是一个字符串,表示发送的邮箱地址
  • return 类型:第三个参数表示生成器生成值后返回的内容。例子中函数返回字符串“Done”

然后调用该生成器

>>> generator = parse_email()
>>> next(generator)
('', '')
#使用 send 方法向生成器发送参数
>>> generator.send("claudia@realpython.com")
('claudia', 'realpython.com')
>>> generator.send("realpython")
'invalid email'
>>> try:
...     generator.send("")
... except StopIteration as ex:
...     print(ex.value)
...
Done

首先调用生成器函数,该函数将返回一个新的 parse_email() 生成器对象。然后,通过调用内置 next() 函数将生成器推进到第一个 yield 语句

之后开始向生成器发送电子邮件地址进行解析。当发送空字符串或不带 @ 符号的字符串时,生成器将终止

又因为生成器也是迭代器,因此也可以使用 collections.abc.Iterator 而不是 Generator 来进行类型提示

但是如果使用了 collections.abc.Iterator 类型提示,就不能指定 send 类型和 rerurn 类型,因此只有当生成器只生成值时 collections.abc.Iterator 才起作用

from collections.abc import Iteratordef parse_emails(emails: list[str]) -> Iterator[tuple[str, str]]:for email in emails:if "@" in email:username, domain = email.split("@")yield username, domain

我们还可以在接收参数里面使用 Iterable 类型提示,这样表示函数 parse_emails 可以接受任何可迭代对象,而不仅仅是像以前那样的列表

from collections.abc import Iterabledef parse_emails(emails: Iterable[str]) -> Iterable[tuple[str, str]]:for email in emails:if "@" in email:username, domain = email.split("@")yield username, domain

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/236443.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决文件导出过大->java压缩zip文件--封装工具类

阿丹: 在业务逻辑中的数据存在一部分业务场景,在导出文件或者视频的时候需要将文件暂存在服务器上再上传到oss对象存储或者fastdfs中让用户来下载使用。但是出现的问题就是如果目标文件过大,文件的上传云端和下载本地就会时间拉长&#xff0c…

使用python读取EXCEL放假日历并制作订阅文件

前言 不想升级IOS,苦于找不到新的日历订阅url,小菜鸡百度来百度去发现ics这东西可以自己做一个,惊喜于看到了这篇文章--使用python获取日历信息并制作订阅文件_https: //github.com/lk-itween/calendar-CSDN博客 感谢作者大大。就想自己写一…

滑动窗口双指针

力扣 209 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 类似窗口滑动 j代表的是窗口的结束位置 i表示开始位置 在while循环中是寻找最…

服务器数据恢复-昆腾存储StorNext文件系统下raid5数据恢复案例

服务器数据恢复环境: 昆腾某型号存储,StorNext文件存储系统。 共有9个分别配置了24块磁盘的磁盘柜,其中8个磁盘柜存放普通数据,1个磁盘柜存放元数据。 存放元数据的磁盘柜中的24块磁盘组建了8组RAID1阵列和1组4盘RAID10阵列&#…

NCV8460ADR2G在汽车和工业应用中高压侧驱动如何破?

NCV8460ADR2G是一款完全保护的高压侧驱动器,可用于开关各种负载,如灯泡、电磁阀和其他致动器。该器件可以通过有源电流限制和高温关断针对过载情况进行内部保护。 诊断状态输出引脚提供了高温以及开关状态开路负载情况的数字故障指示。 特性:…

Nginx的location路径与proxy_pass匹配规则

location路径与proxy_pass匹配规则 路径替换总结当访问地址是 http://127.0.0.1/api/user/list注意:location后斜杆与proxy_pass后斜杆"/"问题,最好要么两者都加斜杆,要么都不加 路径替换 配置proxy_pass时,可以实现URL路径的部分…

22 Vue3中使用v-for遍历对象

概述 使用v-for遍历对象在真实的开发中比较少见,了解即可。 对象我更喜欢统一称之为字典,假如你哪天发现我在某个前端的教程中把对象叫做字典,请你知道这两个是同一个玩意儿。 所谓字典,就是一种key-value类型的结构的统称。 …

队列(C语言版)

一.队列的概念及结构 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有 先进先出 FIFO(First In First Out) 入队列:进行插入操作的一端称为 队尾 出队列:进行删除操作的一端称为…

网络安全之Linux环境配置及Linux基础知识讲解<三>

目录 一.下载安装Vmware二.下载安装Kali三.Linux目录结构四.Linux文件属性五.文件目录管理六.vim编辑器 一.下载安装Vmware Vmware官网:https://www.vmware.com 二.下载安装Kali Kali包含数百种工具,可用于各种信息安全任务,例如渗透测试、…

vue中判断应该import 哪个js或css

import Vue from vue import App from ./App.vueif (window.myFlag true) {import(./jsFile1.js).then((module) > {// 执行导入的jsFile1.js模块的代码module.default.init()}) } else {import(./jsFile2.js).then((module) > {// 执行导入的jsFile2.js模块的代码modul…

vue导出element表格,xlsx和xlsx-style生成xlsx文件并修改样式

1.下载依赖 npm install xlsx --save npm install file-saver --save npm install xlsx-style --save2.先修改xlsx-style的源码,一旦引入xlsx-style则会报错 xlsx-style使用中常见问题及解决办法: xlsx-style使用中常见问题及解决办法-CSDN博客 在\n…

SpringBoot 多环境开发配置文件

在开发过程中,往往开发环境和生产环境需要不同的配置。为了兼容两种运行环境,提高开发效率,可以使用多环境开发配置文件。 配置文件结构大概是这样: application.yml -主启动配置文件(用于控制使用哪种环境配…

Flink系列之:Apache Kafka SQL 连接器

Flink系列之:Apache Kafka SQL 连接器 一、Apache Kafka SQL 连接器二、依赖三、创建Kafka 表四、可用的元数据五、连接器参数六、特性七、Topic 和 Partition 的探测八、起始消费位点九、有界结束位置十、CDC 变更日志(Changelog) Source十一…

Java:获取当前线程的线程组

代码示例: package com.thb;public class Demo4 {public static void main(String[] args) {ThreadGroup threadGroup Thread.currentThread().getThreadGroup();System.out.println(threadGroup.getName());} }运行输出:

“2024山西智博会”由中国人工智能学会和省科学技术协会联合主办

近日,山西省政府新闻办近日举行了“山西加快转型发展”系列主题新闻发布会的第六场发布会,同时也是“推动数字经济发展壮大”专场发布会。在发布会上,省委、省政府强调了数字经济的重要性,并将其作为重组要素资源、重塑经济结构、…

【无标题】欢迎使用Markdown编辑器

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

第五节TypeScript 运算符

一、描述 运算符用于执行程序代码运算。 二、运算符主要包括: 算术运算符逻辑运算符关系运算符按位运算符赋值运算符三元/条件运算符字符串运算符类型运算符 1、算术运算符 y5,对下面算术运算符进行解释: 运算符 描述 例子 x 运算结果…

Jtti:Tomcat服务器底层原理是什么

Apache Tomcat 是一个开源的、轻量级的应用服务器,用于执行Java Servlet、JavaServer Pages (JSP) 和其他相关技术的Web应用程序。以下是 Tomcat 服务器底层的主要原理: Servlet容器: Tomcat 是一个Servlet容器,实现了Java Servle…

ros2 humble安装joint_state_publisher功能包

第一步从GitHub下载此功能包:命令 git clone -b ros2 https://github.com/ros/joint_state_publisher.git 然后执行里面的setup.py文件就完成安装;命令是 sudo python setup.py install 最后检查是不是有安装好,输入命令: ro…

多表插入、删除操作(批量)——后端

多表插入 场景:当添加一个菜品时,还需要记录菜品的口味信息,因此需要对菜品表(dish)和口味表(dish_flavor)同时进行插入操作。 两个表的字段: 代码思路:由DishControll…