高阶Python|返回类型提示技巧 (3)

简介: 高阶Python|返回类型提示技巧 (3)

引言

Python提供了一种可选的特性——类型提示,它有助于提高代码的可读性、可推理性和可调试性。通过类型提示,开发者能够清楚地了解变量、函数参数和返回值应具备的数据类型。在开发那些需要高度灵活性的应用程序时,您可能会需要为代码指定多种可能的返回类型,这样做可以使您的程序更加健壮,更能适应多变的运行环境。

在实际开发中,您可能会碰到需要在Python函数中标注多种返回类型的情况。这意味着函数返回的数据类型不是单一的,而是多样的。本文将通过实例向您展示,如何为一个从电子邮件地址中解析出域名的函数定义多种可能的返回类型。同时,您还将学习到如何为那些接受函数作为参数或者作为回调的函数添加类型提示。通过这些示例,您将能够更自如地在函数式编程中使用类型提示。

利用类型别名提升代码可读性

您已经了解到如何利用类型注解来定义多种类型,现在让我们来看看如何通过最佳实践来提高代码的可维护性。首先是关于类型别名的概念。

如果您在多个函数中反复使用相同的返回类型,那么在代码库的各个地方分别维护它们可能会非常麻烦。在这种情况下,您可以考虑定义一个类型别名。通过为一组类型注安上别名,您可以在代码中的多个函数里复用这个别名。

这样做的好处在于,如果您需要对这组类型注进行修改,只需在一个位置进行更改,而无需逐个函数去调整它们的返回类型。

您可以通过为类型注解定义一个别名,然后用这个别名作为类型注解来实现这一点。以下是对之前函数使用类型别名的一个示例:

EmailComponents = tuple[str, str] | None

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

在这段内容中,您创建了一个名为 EmailComponents 的新变量,用来作为函数返回值类型的别名。这个返回值可以是 None,也可以是一个包含两个字符串的元组。接着,您在函数定义中使用了这个 EmailComponents 别名。

从 Python 3.10 开始,引入了 TypeAlias 语法,让类型别名的声明更加清晰,并且易于与普通变量区分。以下是如何应用这一新特性的示例:

from typing import TypeAlias

EmailComponents: TypeAlias = tuple[str, str] | None

在使用 TypeAlias 对 EmailComponents 别名进行类型注解之前,您需要先从 typing 模块中导入它。导入完成后,您便可以按照之前的例子,将其用作类型别名的类型提示。

请注意,自 Python 3.12 版本起,您可以采用新的软关键字 type 来定义类型别名。软关键字 type 在上下文中明确表示其为关键字时才具有特殊含义,否则它可能被解释为其他意义。别忘了,在 Python 中,type() 同时还是一个内建函数。以下是使用这个新软关键字 type 的示例方法:

type EmailComponents = tuple[str, str] | None

自 Python 3.12 版本起,您可以通过 type 关键字来定义类型别名,正如前述示例所示。这种方式允许您直接指定别名名称及其对应的类型注解。一个显著的优势是,使用 type 定义类型别名时,您无需导入任何额外的模块。

将类型注解赋予具有描述性和实际意义的别名,这种方法虽然简单,却能有效提升代码的可读性和可维护性,因此值得您在编程实践中考虑并应用。

使用静态类型检查工具提升代码质量

Python 作为一门动态类型语言,在运行时不会实际执行类型注解。这表示函数可以声明它想要的任何返回类型,而程序在实际没有返回指定类型值或抛出异常的情况下也能正常运行。

虽然 Python 运行时不强制类型注解,但您可以利用第三方类型检查工具来辅助,这些工具有些还能通过插件与您的代码编辑器结合使用。它们在开发或测试阶段帮助您发现与类型相关的错误。

Mypy 是一个广泛使用的 Python 静态类型检查工具。除此之外,还有 pytype、Pyre 和 Pyright 等其他选择。这些工具通过分析变量的赋值来推断其类型,并与类型注解进行比对。

要在您的项目中应用 mypy,首先需要在您的虚拟环境中通过 pip 命令安装 mypy 包:

(venv) $ python -m pip install mypy

通过这个操作,mypy 工具将被集成到您的项目里。设想一下,如果您在包含之前讨论过的某个函数的 Python 模块上执行 mypy 命令,会看到什么结果?

回想一下先前提到的 parse_email() 函数,它使用一个包含电子邮件地址的字符串作为输入参数。该函数的返回值可能是 None,或者是一个包含用户名与域名信息的字符串元组。如果您还没有将这个函数保存起来,请将其保存在名为 email_parser.py 的文件中。

# email_parser.py

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

您可以通过类型检查器运行此代码,方法是在命令行中键入 mypy,后跟 Python 文件的名称:

(venv) $ mypy email_parser.py
Success: no issues found in 1 source file

这种方式会在不运行您的代码的前提下,自动进行静态代码分析。Mypy 会尝试判断实际的值是否符合类型注解所声明的预期类型。就目前的情况而言,一切似乎都正确无误。

然而,如果您在代码中出了个差错,会怎样呢?例如,如果 parse_email() 函数的返回值类型注解有误,错误地声明为一个字符串,而不是一个包含两个字符串的元组:

# email_parser.py

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

上面修改后的 parse_email() 函数在类型提示和它实际返回的值之一之间存在差异。当您在命令行中重新运行 mypy 时,您将看到以下错误:

(venv) $ mypy email_parser.py
email_parser.py:6: error: Incompatible return value type(got "tuple[str, str]", expected "str | None")  [return-value]
Found 1 error in 1 file (checked 1 source file)

这个提示说明您的函数返回了一个包含两个字符串的元组,而不是预期的单个字符串。这类信息极为重要,因为它有助于避免在程序运行过程中出现严重的错误。

mypy 不仅可以进行类型检查,还能够自动推断类型。当您使用 mypy 运行脚本时,可以将任何表达式或变量传递给 reveal_type() 函数,无需事先导入该函数。reveal_type() 会推断出表达式的类型,并将结果输出到标准输出。

在为函数添加类型注解的过程中,reveal_type() 函数在处理一些较为复杂的情况时非常有用。以下是一个示例,展示了如何利用 reveal_type() 函数来确定 parse_email() 函数返回值的实际类型:

# email_parser.py

# ...

result = parse_email("claudia@realpython.com")
reveal_type(result)

在上面的示例中,变量 result 包含来自解析的电子邮件地址的两个组成部分。您可以将此变量传递到reveal_type()函数中,以便mypy推断类型。以下是在控制台中运行它的方法:

(venv) $ mypy email_parser.py
email_parser.py:10: note: Revealed type is
⮑ "Union[tuple[builtins.str, builtins.str], None]"
Success: no issues found in 1 source file

当您对 Python 文件执行 mypy 检查时,它会输出 reveal_type() 函数所推断的类型信息。例如,mypy 能够准确识别出结果变量是一个包含两个字符串的元组,或者是 None 这种空值。

请务必使用 reveal_type() 这样的实用功能,它可以帮您清晰地了解复杂返回类型的类型信息。

总结

类型注解虽非强制要求,但它能提升代码的可读性、易用性和调试效率。类型注解向其他开发者清晰表明了函数的输入和输出类型,有助于团队协作。

在本教程中,我们专注于在更复杂的场景中应用类型注解,并学习了如何有效使用和维护它们。

通过本教程,您已经学会了:

  • 使用管道符 (|) 或 Union 类型来标注一个函数可能返回的不同类型
  • 使用元组来指定函数返回的多个数据项各自的类型
  • 使用 Callable 类型来注解回调函数
  • 使用 Generator、Iterator 和 Iterable 类型来注解生成器函数
  • 使用类型别名简化代码中多处使用的复杂类型注解
  • 应用 Mypy 这个第三方类型检查工具

现在,您可以在多种不同的编程情境中运用类型注解。您通常是如何在代码中使用类型注解的?欢迎在评论区分享您的实际应用案例。

相关文章
|
6天前
|
存储 索引 Python
Python散列类型(1)
【10月更文挑战第9天】
|
11天前
|
计算机视觉 Python
Python实用记录(一):如何将不同类型视频按关键帧提取并保存图片,实现图片裁剪功能
这篇文章介绍了如何使用Python和OpenCV库从不同格式的视频文件中按关键帧提取图片,并展示了图片裁剪的方法。
38 0
|
4天前
|
存储 数据安全/隐私保护 索引
|
11天前
|
Python
【10月更文挑战第6天】「Mac上学Python 11」基础篇5 - 字符串类型详解
本篇将详细介绍Python中的字符串类型及其常见操作,包括字符串的定义、转义字符的使用、字符串的连接与格式化、字符串的重复和切片、不可变性、编码与解码以及常用内置方法等。通过本篇学习,用户将掌握字符串的操作技巧,并能灵活处理文本数据。
46 1
【10月更文挑战第6天】「Mac上学Python 11」基础篇5 - 字符串类型详解
|
11天前
|
Python
【10月更文挑战第6天】「Mac上学Python 10」基础篇4 - 布尔类型详解
本篇将详细介绍Python中的布尔类型及其应用,包括布尔值、逻辑运算、关系运算符以及零值的概念。布尔类型是Python中的一种基本数据类型,广泛应用于条件判断和逻辑运算中,通过本篇的学习,用户将掌握如何使用布尔类型进行逻辑操作和条件判断。
46 1
【10月更文挑战第6天】「Mac上学Python 10」基础篇4 - 布尔类型详解
WK
|
5天前
|
存储 Python
Python内置类型名
Python 内置类型包括数字类型(int, float, complex)、序列类型(str, list, tuple, range)、集合类型(set, frozenset)、映射类型(dict)、布尔类型(bool)、二进制类型(bytes, bytearray, memoryview)、其他类型(NoneType, type, 函数类型等),提供了丰富的数据结构和操作,支持高效编程。
WK
9 2
|
7天前
|
存储 编译器 索引
Python 序列类型(2)
【10月更文挑战第8天】
Python 序列类型(2)
|
8天前
|
存储 C++ 索引
Python 序列类型(1)
【10月更文挑战第8天】
|
16天前
|
存储 Java Apache
Python Number类型详解!
本文详细介绍了 Python 中的数字类型,包括整数(int)、浮点数(float)和复数(complex),并通过示例展示了各种算术操作及其类型转换方法。Python 的 `int` 类型支持任意大小的整数,`float` 类型用于表示实数,而 `complex` 类型用于表示复数。此外,文章还对比了 Python 和 Java 在数字类型处理上的区别,如整数类型、浮点数类型、复数类型及高精度类型,并介绍了各自类型转换的方法。尽管两种语言在语法上有所差异,但其底层逻辑是相通的。通过本文,读者可以更好地理解 Python 的数字类型及其应用场景。
30 2
|
17天前
|
Java 程序员 C++
【Python】动态类型、输入和输出、条件语句
【Python】动态类型、输入和输出、条件语句
19 0