Python 的 Self 类型 | 注释返回 Self 类型的参数

2024年8月29日 | 阅读 8 分钟

有时,我们在庞大的 Python 代码库中迷失方向,难以跟踪变量的预期类型。类型提示和注释在这种情况下可以帮助覆盖变量类型。在本教程中,我们将讨论返回自身或类其他实例的注释方法。我们在上一教程中已经讨论了各种类型的注释和方法。

如何在 Python 中使用 Self 类型注释函数

从 Python 3.11 及更高版本开始,PEP 673 定义的 Self 类型是返回类实例的函数的推荐注释。Self 类型可以直接从 `typing` 模块导入。

但是,对于 3.11 之前的 Python 版本,Self 类型在 `typing_extensions` 模块中可用。您可以从那里导入它,用于注释返回类实例的函数。

以下是如何导入和使用 Self 类型的示例

示例 -

在这两种情况下,Self 类型都用作 `self` 参数的注释以及函数的返回类型。这表明该函数返回一个属于其自身的类实例。

请注意,对于 3.11 之前的 Python 版本,如果 `typing_extensions` 包未安装,我们将需要安装它。我们可以使用 pip 安装它:

通过使用 Self 类型,我们可以为返回类实例的函数提供清晰简洁的注释,从而提高代码的可读性和可维护性。

现在,让我们看一下表示银行账户状态和逻辑的类的结构。BankAccount 类包含各种选项,例如存入和取出资金,这些操作会更新账户状态并返回类实例。让我们理解以下示例。

示例 -

Self 类型用作返回类实例的函数的注释。

`@dataclass` 装饰器应用于 BankAccount 类,以根据定义的字段自动生成 `__init__`、`__repr__`、`__eq__` 等特殊方法的默认实现。

我们可以创建一个 BankAccount 对象并如下使用函数:

上面的代码创建了一个 BankAccount 对象,其账号为 123456,初始余额为 1000.0 卢比。然后它显示账户余额,存入 500.0 卢比,取出 200.0 卢比,并再次显示更新后的余额。

使用 Self 类型可以实现函数链式调用,从而在对 BankAccount 对象执行多个操作时实现简洁可读的代码。

Self 类型也可以用于类方法和继承层级结构。例如,如果父类和子类具有返回自身的函数,我们可以用 Self 类型注释两者。

我们正在创建一个继承自 BankAccount 的 SavingsAccount 类。

示例 -

`create_from_application` 函数是一个类方法(用 `@classmethod` 装饰),它根据提供的初始存款和利率创建 SavingsAccount 对象。它使用 `random.randint` 函数生成一个随机的七位数账号,然后实例化并返回一个新的 SavingsAccount 类实例,其中包含生成的账号、初始存款和利率。

`calculate_interest()` 函数根据当前余额和利率计算储蓄账户的利息。它将余额乘以利率除以 100,然后返回计算出的利息。

`add_interest()` 函数将计算出的利息添加到储蓄账户。它调用 `calculate_interest()` 函数来确定利息金额,然后使用 `deposit` 函数(可能从 BankAccount 类继承)将利息添加到账户余额。最后,它返回 self,从而可以进行函数链式调用,并在向账户添加利息时实现简洁的代码。

我们现在可以使用 `create_from_application()` 函数创建一个 SavingsAccount 对象。

使用 TypeVar 注释

注释返回类实例的函数的另一种方法是利用 TypeVar。TypeVar 代表一个类型变量,它在类型检查期间充当特定类型的占位符。它通常用于处理泛型类型,例如指定特定对象的列表,如 `list[str]` 或 `list[BankAccount]`。

通过使用 `TypeVar`,您可以定义一个代表当前类的类型变量,并将其用作返回该类实例的函数的注释。这允许更灵活和通用的类型注释。

以下是一个示例来说明这种方法。

示例 -

在此示例中,T 是使用 TypeVar 定义的类型变量。它代表当前类,允许进行通用注释。 `some_method()` 函数用 T 作为返回类型进行注释,表示它返回类的实例。

通过以这种方式使用 TypeVar,您可以为返回类实例的函数提供更通用和灵活的类型注释。

一个受类约束的 TypeVar 可以实例化为该类的任何子类。此功能在 BankAccount 和 SavingsAccount 示例的上下文中特别有用。

示例 -

正如我们所看到的,TBankAccount 被绑定到 BankAccount,现在我们可以正确地注释在 BankAccount 中返回自身的函数。

解释 -

在上面的代码中,我们用 TBankAccount 注释了 `.display_balance()` 以指定它将返回类实例。关键是要记住 TBankAccount 与 BankAccount 不同。它仅用于在无法直接使用 BankAccount 的注释中表示 BankAccount 类型。

出于多种原因,Self 类型通常优于 TypeVar。TypeVar 的主要缺点之一是它可能很冗长,导致更复杂的类型注释。开发人员也可能意外地忘记实例化 TypeVar 或正确地将其绑定到类,从而导致潜在的类型相关问题。

另一方面,Self 类型(在 Python 3.11 中引入,并在早期版本中可在 typing_extensions 中使用)提供了更简单、更简洁的语法。它明确地将类实例表示为返回类型,从而无需使用 TypeVar。

Self 类型提高了代码的可读性,并降低了与类型注释相关的错误或疏忽的风险。它还享有更好的 IDE 支持,因为大多数现代 IDE 都可以理解并根据 Self 类型提供准确的建议。

考虑到这些原因,Self 类型通常比 TypeVar 更受青睐,用于注释返回类实例的函数,因为它具有直观简洁的语法、提高的代码可读性和更好的 IDE 支持。

使用 `__future__` 模块

Python 的 `__future__` 模块提供了各种方法来注释返回封闭类的函数。有时,此模块用于引入与未来 Python 版本兼容的不兼容更改。

注释可以使用大于 3.7 的 Python 版本导入。让我们理解以下示例。

示例 -

在上面的代码中,我们从 `__future__` 模块导入了注释。此模块在低于 3.7 的版本中不可用。我们直接使用类名作为 `.push()` 的注释。

使用 `future` 模块使用类名注释函数不被认为是最佳实践,因为它不如使用 Self 类型直观和 Pythonic。此外,必须记住在脚本开头从 future 导入可能会很麻烦。而且,值得注意的是,在用 future 注释时,继承的支持不足。

使用字符串进行类型提示

注释返回类实例的函数的另一种选择是使用字符串。此方法推荐用于 3.7 之前的 Python 版本或当其他方法均不适用时。字符串注释不需要任何导入,并且被大多数静态类型检查器广泛识别。

示例 -

在这种情况下,在字符串注释中包含类名很重要。省略类名将导致静态类型检查器无法将返回类型识别为有效的 Python 对象。字符串注释直接实现了与 `__future__` 注释的后台功能类似的结果。

字符串注释的一个重要限制是它们在继承期间不会保留其信息。当子类从超类继承函数时,超类中指定的字符串注释不会自动转移到子类。因此,如果您依赖字符串注释进行类型提示或文档记录,则必须在每个子类中声明注释。此过程可能容易出错且耗时。

结论

在 Python 中使用类型提示和注释可以提高代码的可读性和可维护性,特别是当代码变得更大、更复杂时。通过指定变量、函数参数和返回值的类型,您可以帮助其他开发人员理解预期的变量类型以及函数调用的预期结果。

Self 类型是一种特定的类型提示,可用于注释返回自身类实例的函数。通过使用 Self 类型,返回类型变得显式,这在防止继承和子类化期间可能发生的细微错误方面可能是有利的。尽管可以使用 TypeVar、`__future__` 模块和字符串等替代方法来注释返回类实例的函数,但建议在可行的情况下使用 Self 类型。


下一主题追踪鸟类迁徙