首页 > 编程语言 >Python 中如何编写类型提示

Python 中如何编写类型提示

时间:2023-12-18 18:24:59浏览次数:35  
标签:... 函数 Python 生成器 提示 str 类型 编写 email

哈喽大家好,我是咸鱼

我们知道 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, "[email protected]")
('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("[email protected]")
('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,生成器,提示,str,类型,编写,email
From: https://www.cnblogs.com/edisonfish/p/17911868.html

相关文章

  • 用DE10_NANO_SOC_FB生成dts的时候 提示Component alt_vip_itc_0 of class alt_vip_itc
    当使用DE10_NANO_SOC_FB工程产生dts时提示Componentalt_vip_itc_0ofclassalt_vip_itcisunknown: 如果此时生成了dtb,可以忽略这个提示。  如果没有生成,您可以先在Qsys中把这两个器件disable掉,然后top文件里面把相关的信号也注释掉,然后重新生成soc_system.......
  • python123——numpy、scipy、pandas、matplotlib的读书报告
     一、函数的基本用法numpyNumPy(NumericalPython)是Python的一种开源的数值计算扩展。这种工具可用来存储和处理大型矩阵,比Python自身的嵌套列表(nestedliststructure)结构要高效的多(该结构也可以用来表示矩阵(matrix)),支持大量的维度数组与矩阵运算,此外也针对数组运算提供大量的......
  • Python 如何读取 Excel 表内容
    在数据处理和分析中,Excel是一个常用的工具,而使用Python读取Excel表内容可以更方便地进行数据分析和处理。本文将介绍几种常见的Python库和方法,帮助您实现读取Excel表内容的操作。使用openpyxl库读取Excel表内容openpyxl是一个用于读写Excel文件的Python库,支持Excel2010及以上版本......
  • python怎么统计文档中字母出现的次数
    当你需要统计文档中字母出现的次数时,Python提供了许多方法和库来方便地完成这个任务。下面将介绍两种常见的方法:使用纯Python和使用第三方库。方法一:使用纯Python使用纯Python的方法可以帮助你统计文档中字母出现的次数。以下是一个简单的示例代码:```python#打开文档并读取内容wi......
  • python self是什么意思?怎么使用?
    在Python中,self是一个特殊的参数,通常用作方法的第一个参数。它表示对类实例自身的引用,类似于其他编程语言中的this关键字。self参数的命名可以是任意合法的标识符,但按照惯例,大多数开发者都使用self。为什么需要self参数?使用self参数的目的是允许类的实例访问其自身的属性和方法。通......
  • Python 删除行列的操作方法有哪些
    在Python中,删除行和列的操作通常用于处理二维数组、矩阵或数据框等数据结构。通过删除不需要的行列,可以使数据结构更加紧凑和易于处理。本文将介绍几种Python中删除行列的操作方法。删除行在Python中,删除行的操作方法有多种,以下是其中的几种方法:方法一:使用`del`关键字使用`del`关键......
  • 如何在 Python 的 while 循环中只打印一次
    在Python的while循环中,我们有时候需要打印一些信息或者执行某些操作,但是我们希望这些操作只执行一次,而不会影响循环的进行。本文将介绍在Python的while循环中如何只打印一次,而不破坏循环的进行。使用标志位使用标志位是在Python的while循环中只打印一次的常用方法。标志位可以是任......
  • 如何利用 python 制作词云图
     制作词云图是一种可视化文本数据的方法,可以通过展示词频来展示文本中的关键词信息。在Python中,我们可以使用第三方库`wordcloud`来制作词云图。本文将介绍如何利用Python制作词云图。 安装依赖库 在开始之前,我们需要先安装`wordcloud`库和`matplotlib`库。可以使用以下命令进......
  • Ubuntu20.04下DeepStream Python环境安装
    引子最近工作学习中遇到多路视频解码抽帧的需求,考虑到项目上大多数用到的都是Nvidia的显卡,常规CPU软解显然无法满足多路的需求,故考虑使用N卡的硬解码功能。然后我就毫不犹豫的去找轮子了,ChatGPT这么火,那就先问问它吧。嗯,呃,貌似下图红框里答案只有那么一点点靠谱(不要问我为啥......
  • python celery的使用
    celery本生就不介绍了感兴趣的看https://c.biancheng.net/view/s0j4eth.html这个人家介绍的挺好的1.安装部署Celery涉及任务队列和结果存储,我们使用Redis,做例子前要先安装好redis。我们可以通过命令行方式下载和安装指定版本:#安装Celerypipinstallcelery==5.2.3#......