哈喽大家好,我是咸鱼。
我们知道 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 ,要么返回 Nonestr | 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 username return 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, Uniondef 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 Callablefrom 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 Callablefrom 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]类型提示里面有三个参数(后面两个是可选的),其中:
然后调用该生成器:
>>> 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 可以接受任何可迭代对象,而不仅仅是像以前那样的列表。
本文链接:http://www.28at.com/showinfo-26-49478-0.htmlPython 中如何编写类型提示
声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com
上一篇: 为什么微服务的测试必须左移