哈喽大家好,我是咸鱼

我们知道 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 username
return None

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

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

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

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

from typing import Union

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

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

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

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

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

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

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

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

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

from typing import Tuple, Union

def parse_email(email_address: str) -> Union[Tuple[str, str], None]:
if "@" in email_address:
username, domain = email_address.split("@")
return username, domain
return 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 Any def 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, TypeVar T = 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 Iterator

def 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 Iterable

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

Python 中如何编写类型提示的更多相关文章

  1. Python中的鸭子类型

    今天,我们来聊一聊Python中的鸭子类型(duck typing). 编程语言具有类型概念,例如Python中有数字类型.字符串类型.布尔类型,或者更加复杂的结构,例如元组tuple.列表list. ...

  2. Python中内置数据类型list,tuple,dict,set的区别和用法

    Python中内置数据类型list,tuple,dict,set的区别和用法 Python语言简洁明了,可以用较少的代码实现同样的功能.这其中Python的四个内置数据类型功不可没,他们即是list, ...

  3. Python 中的枚举类型~转

    Python 中的枚举类型 摘要: 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期.月份.状态等. 枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表 ...

  4. python中对象、类型和元类之间的关系

    在python中对象.类型和元类构成了一个微妙的世界. 他们有在这个世界里和平共处,相辅相成.它们遵循着几条亘古不变的定律: 1.python中无处不对象 2.所有对象都有三种特性:id.类型.值 3 ...

  5. Python中的集合类型分类和集合类型操作符解析

    集合类型    数学上,把set称作由不同的元素组成的集合,集合(set)的成员通常被称作集合元素(set elements).    Python把这个概念引入到它的集合类型对象里.集合对象是一组无 ...

  6. Python 中的鸭子类型和猴子补丁

    原文链接: Python 中的鸭子类型和猴子补丁 大家好,我是老王. Python 开发者可能都听说过鸭子类型和猴子补丁这两个词,即使没听过,也大概率写过相关的代码,只不过并不了解其背后的技术要点是这 ...

  7. python中使用 C 类型的数组以及ctypes 的用法

    Python 在 ctypes 中为我们提供了类似C语言的数据类型, 它的用途(我理解的)可能是: (1) 与 其他语言(如 C.Delphi 等)写的动态连接库DLL 进行交换数据,因为 pytho ...

  8. Python中的基本类型简介

    1.变量 变量不仅可以是数字,还可以是任意数据类型 命名规范:变量是用一个变量名表示,变量名必须是大小写英文.数字和下划线_的组合,且不能用数字开头 python中等号“=”是赋值语句,可以把任意数据 ...

  9. Python学习笔记整理(三)Python中的动态类型简介

    Python中只有一个赋值模型 一.缺少类型声明语句的情况 在Python中,类型是在运行过程中自动决定的,而不是通过代码声明.这意味着没有必要事声明变量.只要记住,这个概念实质上对变量,对象和它们之 ...

  10. 顺序表的原理与python中的list类型

    数据是如何在内存中存储的? 在32位的计算机上,1个字节有8位,内存寻址的最小单位就是字节.假设我们有一个int类型的值,它从0x10开始,一个int占据4个字节,则其结束于0x13. 那么数据类型有 ...

随机推荐

  1. 作为一个客户经理你一个如何给客户介绍API接口

    随着科技的发展,API(Application Programming Interface,应用程序接口)的应用已经逐渐普及,而API接口作为现代企业实现智能化运营和管理的重要工具之一,也备受关注.作 ...

  2. 从驾考科目二到自动驾驶,聊聊GPU为什么对自动驾驶很重要

    "下一个项目,坡道起步." -- "考试不合格,请将车子开到起点,重新验证考试.你的扣分项是:起步时间超30秒:扣100分.行驶过程中车轮轧到边线:扣100分." ...

  3. zabbix 警报推送至企业微信(图文版)

    新增Python脚本 # encoding: utf-8 import sys import requests import json import os import time import re ...

  4. 大模型时代,如何快速开发AI应用

    本文分享自华为云社区 <[云享问答]第3期:大模型时代,如何快速开发AI应用>,作者:华为云社区精选. 大模型快速普及应用的当下,AI浪潮汹涌而至,对于开发者来说,开发一款属于自己的AI应 ...

  5. 《流畅的Python》 读书笔记 230926(第一章后半部分)

    1.2 如何使用特殊方法 特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们 就是说通常你都应该用len(obj)而不是obj.__len()__,无论是系统预置的,还是你自己 ...

  6. jmeter生成HTML性能测试报告(非GUI的命令)

    非GUI的命令(在cmd执行即可 不需要打开jmeter) 使用命令:jmeter  -n  -t  [jmx file]  -l  [jtl file]  -e  -o   [report path ...

  7. DHorse v1.4.2 发布,基于 k8s 的发布平台

    版本说明 优化特性 在集群列表增加集群版本: 修改Jvm的GC指标名: 解决问题 解决shell脚本换行符的问题: 解决部署历史列表页,环境名展示错误的问题: 解决指标收集功能的异常: 升级指南 升级 ...

  8. 每天5分钟复习OpenStack(四) virsh 常用命令

    在上一章节中,我们拉起了第一台虚拟机,但是执行virsh shutdown 关机是无法关机的,需要使用virsh destroy 强制断电的命令来关机.为什么会这样了? 这里我们介绍下 QGA的概念 ...

  9. 【matplotlib 实战】--散点图

    散点图,又名点图.散布图.X-Y图,是将所有的数据以点的形式展现在平面直角坐标系上的统计图表. 散点图常被用于分析变量之间的相关性.如果两个变量的散点看上去都在一条直线附近波动,则称变量之间是线性相关 ...

  10. 从0到1实现 OpenTiny 组件库跨框架技术

    本文分享自华为云社区<从0到1实现 OpenTiny 组件库跨框架技术>,作者:华为云社区精选 . 在华为云<DTSE Tech Talk>技术直播第44期<0基础玩转 ...