哈喽大家好,我是咸鱼

我们知道 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. MIT 6.828 Lab实验记录 —— lab1 Booting PC

    实验参考信息 MIT 6.828 lab1 讲义地址 MIT 6.828 课程 Schedule MIT 6.828 lab 环境搭建参考 MIT 6.828 lab 工具guide Brennan' ...

  2. docker搭建dvwa靶场

    查看有哪些DVWA镜像 docker search dvwa 这里选择拉取第一个镜像 docker pull citizenstig/dvwa 访问 https://hub.docker.com/ 搜 ...

  3. 3天上手Ascend C编程丨通过Ascend C编程范式实现一个算子实例

    本文分享自华为云社区<3天上手Ascend C编程 | Day2 通过Ascend C编程范式实现一个算子实例>,作者:昇腾CANN . 一.Ascend C编程范式 Ascend C编程 ...

  4. 利用别名简化进入docker容器数据库的操作

    之前研究docker和数据库的交互,越发对docker这个东西喜爱了.因为平常偶尔会用到各类数据库测试环境验证一些想法,需要进一步简化进入到这些环境的步骤. 比如我现在有三套docker容器数据库测试 ...

  5. k8s work节点无法使用kubectl命令

    在Kubernetes的node节点上运行命令 [ kubectl ] 命令出现了如下错误 root@calico-work01:~# kubectl get nodes The connection ...

  6. math 库中常用的数学运算和常量【GO 基础】

    〇.关于 math GO 语言的 math 库是一个内置的标准库,其中包含了许多数学函数和常量,用于计算各种数学运算和统计学计算. 日常开发中,计算当然是少不了的,那么今天来梳理下备查. 一.测试示例 ...

  7. Gossip in Hyperledger Fabric

    1. Gossip协议基础 1.1 什么是分布式系统 分布式系统(Distributed System)是由多台计算机或计算节点组成的计算机系统,这些计算节点通过网络连接在一起,并协同工作以完成共同的 ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (115)-- 算法导论10.2 8题

    八.用go语言,说明如何在每个元素仅使用一个指针 x.np(而不是通常的两个指针 next和prev)的下实现双向链表.假设所有指针的值都可视为 k 位的整型数,且定义x.np=x.next XOR ...

  9. Shuffle 题解

    Shuffle 题目大意 给定一个长度为 \(n\) 的 01 序列 \(a\),你可以进行至多一次以下操作: 选定 \(a\) 的一个连续段,满足连续段内恰好有 \(k\) 个 \(1\),将该连续 ...

  10. PTA乙级1099

    #include"bits/stdc++.h" using namespace std; int prime(int x) { if(x<2) return 0; for(i ...