作者:facebook

原文链接:Why Today’s Python Developers Are Embracing Type Hints

Python 是目前最成功的编程语言之一,根据最新的 GitHub Octoverse 报告,它最近已经超过了 Javascript,成为 GitHub 上最受欢迎的语言。该报告强调了该语言在人工智能、数据科学和科学计算等快速增长领域的流行——这些领域需要快速实验和迭代,而开发者来自广泛的 STEM 背景,不一定都是计算机科学专业。但随着 Python 社区的扩展以及项目从实验转向生产系统,这种灵活性也可能成为一种负债。

这就是为什么今天我们要讨论带类型的 Python——它是什么,为什么它对当今的 Python 开发者变得重要,以及如何开始使用它来编写更高质量、更可靠的代码。

什么是带类型的 Python?

在我们深入探讨为什么你应该在日常开发中使用带类型的 Python 之前,首先我们需要理解一些核心概念以及我们是如何到达这里的。

动态类型与静态类型

你所熟悉和喜爱的经典 Python 编程语言是动态类型的。这具体是什么意思呢?这意味着类型是在运行时确定的,而不是在你编写代码时确定的。变量可以持有任何类型的值,你不需要声明它们是什么类型。

这里有一个动态类型作用的例子:

x = 5        # x is an integer
x = "hello"  # now x is a string
x = [1,2,3]  # now x is a list

这种行为是 Python 与 Java 或 C++ 等静态类型语言区别开来的一项特性,后者要求从一开始就声明类型:

int x = 5;
std::string x_str = "hello";
std::vector<int> x_vec = {1, 2, 3};

在上面的例子中,我们不能随意将变量 x 重新赋值为任何类型的值,它只能持有整数,因为 C++ 语言的静态类型特性。

Python 是一种动态类型语言,这是它易于使用并受到新手和经验丰富的程序员欢迎的原因之一。它使得开发快速演示、实验研究和概念验证变得容易,而无需花费宝贵的时间声明类型。这种灵活性在人工智能、数据科学和科学计算领域的应用中起到了关键作用,研究人员需要快速迭代和实验不同的方法。

然而……(当然你知道会有“但是”的)

我们正迅速地超越了许多这些行业的“概念验证”阶段。人工智能和机器学习工作正被积极集成到生产应用程序中,随之而来的是对可靠性和稳定性的生产级期望。依赖动态类型会使这些代码库面临一定程度的风险,而这种风险可能无法接受它们现在被期望的规模。

引入 PEP 484:静态类型来到 Python

回想一下 2014 年 9 月:德国刚刚赢得世界杯,紧身牛仔裤仍然流行,泰勒·斯威夫特的《Shake it Off》在排行榜上排名第一。就在同一个月,PEP 484 首次被创建,提议将类型提示添加到 Python 中,并从根本上改变了未来开发者能够编写和维护 Python 代码的方式。

随着 PEP 484 在 Python 3.5 中的接受和引入,开发者现在可以使用静态类型注解来声明函数参数和返回值的预期数据类型,后续的 PEP 不断添加更多功能来扩展和完善 Python 的类型系统。今天你可以像这样编写静态类型的 Python 语句:

def my_func(x: int, y: str) -> bool:
    z: str = str(x)
    return z == y

PEP 484 的关键创新是引入了一种渐进式类型系统,允许开发人员逐步添加类型注解,而不会破坏现有代码。该系统的工作原理如下:

  • 仅对具有明确返回值或参数注解的函数进行类型检查
  • 引入 Any 类型作为逃生通道,该类型具有所有可能的属性
  • 假设未注解的函数隐式返回 Any

这种方法意味着开发者可以逐步采用类型注解,同时仍然允许他们利用 Python 默认的动态类型方法,这使得 Python 易于使用,并且非常适合快速原型设计。

Python 类型提示的优势:更快地编写更好质量的代码

那么,为什么你特别应该开始在 Python 代码中使用类型提示呢?Python 类型提示提供了一系列优势,可以显著提高代码的质量、可维护性和可扩展性,同时使其他开发者更容易理解你的代码并与你协作。

类型能帮你尽早捕获错误

类型提示有助于静态分析工具在代码执行前识别不匹配和潜在错误,从而实现早期错误检测。以下是一个示例:

def add_numbers(a, b):
       return a + b
...
add_numbers(3, "4")  # Potential error

当你在函数定义处附近调用该函数时,上述错误可能很容易被发现,但想象一下你在处理多个文件和/或有很多行代码将它们隔开——突然就不再那么容易了!

相比之下,如果你在使用类型提示的同时配合一个类型检查工具(例如 Pyrefly 或 MyPy),你可以在编写代码时更早地捕获这个错误,而不是在运行时出错:

def add_numbers(a: int, b: int) -> int:
  return a + b
...
add_numbers(3, "4")  # a typechecker will catch this error at time of writing

使用类型检查器来突出这些类型的错误,还可以确保即使您在单元测试中遗漏了这段代码路径,也能捕获到这种错误。

类型化的代码具有自描述性

编写类型化 Python 的另一个好处是,使用函数签名和变量注解为特定代码片段提供了意图的清晰性。换句话说,它使代码更易于阅读和审查。它使重构更安全、更可预测。它甚至有助于新团队成员快速了解代码库中的情况,而不会浪费他们自己的时间,或你的时间!

以以下示例为例,如果没有类型提示,您必须仔细阅读内部函数代码才能了解哪些类型的参数有效以及返回什么类型:

def calculate_stats(data, weights):
    total = 0
    weighted_sum = 0
    for i, value in enumerate(data):
        if i < len(weights):
            weighted_sum += value * weights[i]
            total += weights[i]
    avg = weighted_sum / total if total > 0 else 0
    return avg, len(data)

在这个版本中,您可以立即知道应该传递哪种类型的参数以及应该期望得到什么结果,从而节省宝贵的开发时间,并使您的生活更轻松:

def calculate_stats(data: list[float], weights: list[float]) -> tuple[float, int]:
    total = 0
    weighted_sum = 0
    for i, value in enumerate(data):
        if i < len(weights):
            weighted_sum += value * weights[i]
            total += weights[i]
    avg = weighted_sum / total if total > 0 else 0
    return avg, len(data)

我知道我知道——理想情况下,所有开发者都应该在编写的每个函数旁边添加清晰的文档字符串,但我们知道在现实中并不总是能如愿以偿!添加类型提示比编写典型的文档字符串更快,不会过时(如果使用类型检查器强制执行),并且比完全没有文档要好。现代 Python 类型检查器还提供了包含自动完成功能的 IDE 扩展,以简化工作。

使用类型提示的 Python 帮助您从概念验证扩展到生产就绪

使用类型注解在代码中的一个最重要的好处是它可以帮助你更快、更低风险地扩展代码。对于当今的开发者来说,从实验代码到生产系统的流程比以往任何时候都快,特别是在人工智能和机器学习工作流中,研究原型必须迅速演变成健壮、可扩展的应用程序。

例如,假设有一个数据科学团队刚刚发表了他们的研究成果,现在需要将他们的模型投入运行。如果他们发布的代码已经包含了类型提示,那么工程团队介入并将这项研究集成到生产应用程序中就会变得更容易、更快、更安全。在这种情况下,类型注解充当了开发不同阶段之间的合同,清楚地说明了数据如何流经复杂的、多步骤的处理管道。这在人工智能工作流中尤其有价值,因为单个类型不匹配,比如在预期 PyTorch 张量时传递了一个 NumPy 数组,可能会导致静默失败或性能下降,而这些问题只有在生产负载下才会暴露出来。

立即开始使用带类型提示的 Python!

所以现在你知道了什么是带类型的 Python 以及为什么要这样做,那么你实际上该如何开始将类型添加到你的代码中呢?

第零步 - 尽早开始!

一般来说,在项目中越早开始添加类型注解越好。类型提示在开发过程中逐步添加比后期在整个代码库中重构要容易得多。

正如我们之前提到的,Python 的一大优势在于其动态类型默认设置使其非常灵活且易于上手。所以当您在进行初步的实验和原型设计时,也许您并没有考虑确保其类型安全——这完全可以接受!但一旦您开始认为您的项目可能会有所发展,如果有多于一个人可能参与其中、使用它或仅仅阅读它,您就应该开始添加类型提示。

第一步 - 安装类型检查器

选择并安装适合您需求的类型检查器。类型检查器利用您编写的代码注释来提供重要的错误和警告,以确保您的代码库类型安全。

在 Meta,我们推荐 Pyrefly,这是我们用 Rust 构建的新开源类型检查器。Pyrefly 设计用于从小型项目扩展到庞大的代码库,同时提供出色的开发者体验。阅读 Pyrefly 文档以了解配置选项和最佳实践,然后开始向新函数添加简单的类型提示,再逐步扩展到更复杂的场景。

您还应该考虑使用支持 IDE 集成的类型检查器,以便在编写代码时获得实时反馈。Pyrefly 为 VS Code、PyCharm 和 Vim 等编辑器提供扩展,这些扩展将突出显示错误并提供基于您的类型注解的自动完成建议。

将类型检查器添加到您的 CI 流程中对于在大规模上保持代码质量也是很有价值的。您可以配置您的 CI/CD 管道,使其在每次拉取请求时运行类型检查,并将类型错误视为构建失败。

第二步 - 利用资源提升打字技巧

打字是一项随着你在代码中练习得越多而越能提升的技能,但也有许多优质资源可以帮助你掌握其功能并深入理解概念:

  • 官方 Python 类型提示文档 - 类型模块文档提供了所有可用类型的全面覆盖
  • PEP 484 及相关 PEP - 理解基础规范有助于你掌握类型决策背后的"原因"
  • 为您的类型检查器选择文档,例如在学习类型方面的 Pyrefly 文档
  • 加入社区论坛并获取支持,例如 Pyrefly Discord、Typing Discourse

结论

好了,这就为你简要介绍了 Python 类型注解的世界!现在,你大概已经了解到类型注解不仅仅是 Python 众多特性中的一个,你最终肯定会实现它们——它们是对你代码未来的实际投资。添加类型注解的前期工作会在减少调试会话、使代码审查更顺畅以及减少生产问题方面带来回报。最重要的是,它们让你有信心重构和扩展你的代码库,而不用担心以意想不到的方式破坏东西。从小处着手,给你的下一个函数添加类型注解,将类型检查器添加到你的工作流程中,很快你就会发现编写带类型注解的 Python 会变得自然而然。你的未来自己(以及你的用户和团队成员!)会感谢你。