类型检查400万行Python代码的方式。 第一部分

今天,请您注意有关Dropbox如何参与Python代码的类型控制的材料翻译的第一部分。



Dropbox用Python写了很多东西。 这是我们广泛使用的语言-既用于后端服务,又用于桌面客户端应用程序。 我们还大量使用Go,TypeScript和Rust,但是Python是我们的主要语言。 考虑到我们的规模,并且我们正在谈论数以百万计的Python代码行,事实证明,这种代码的动态类型不必要地加深了对其的理解,并开始严重影响生产力。 为了缓解此问题,我们开始使用mypy将代码逐步转换为静态类型检查。 这可能是最流行的Python独立类型检查系统。 Mypy是一个开源项目;其主要开发人员在Dropbox工作。

Dropbox是最早以类似规模在Python代码中实现静态类型检查的公司之一。 如今,mypy已用于数千个项目中。 就像他们所说的那样,此工具“经受了考验”的次数无数次。 为了到达现在的位置,我们必须走很长一段路。 在这条道路上,有许多失败的尝试和失败的实验。 这篇材料是关于Python静态类型检查的历史的-从它非常艰难的开始(这是我的科研项目的一部分)到今天,无数使用Python编写的开发人员都对类型检查和类型提示很熟悉。 现在,许多工具都支持这些机制,例如IDE和代码分析器。

阅读第二部分

为什么需要类型检查?


如果您曾经使用过动态类型的Python,那么您可能会对为什么最近围绕静态类型和mypy产生如此嗡嗡声感到困惑。 也可能恰恰是因为它的动态类型而使您喜欢Python,而发生的事情只会使您感到不安。 静态类型价值的关键在于决策的规模:项目越大,您倾向于静态类型的可能性就越大,最后,您真正需要的类型就越大。

假设一个项目达到成千上万的行,结果证明有几个程序员正在处理它。 考虑到这样一个项目,根据我们的经验,我们可以说理解其代码将是支持开发人员生产力的关键。 如果没有类型注释,就很难弄清楚要传递给函数的参数,或者函数可以返回哪些类型的值。 以下是不使用类型注释通常很难回答的典型问题:

  • 此函数可以返回None吗?
  • 这个items论点应该是什么?
  • id是什么: int属性类型,是str还是某种自定义类型?
  • 此参数应为列表吗? 是否可以将元组传递给它?

如果查看下面的带有类型注释的代码片段,并尝试回答此类问题,那么事实证明这是最简单的任务:

 class Resource:    id: bytes    ...    def read_metadata(self,                      items: Sequence[str]) -> Dict[str, MetadataItem]:        ... 

  • read_metadata不会返回None ,因为返回类型不是Optional[…]
  • items参数是一个字符串序列。 不能以任何顺序进行迭代。
  • id属性是一个字节字符串。

在理想的世界中,人们希望所有这些细微差别都将在内置文档(文档字符串)中进行描述。 但是经验提供了很多这样的事实,即您经常使用的代码中的此类文档通常未被遵守。 即使代码中存在此类文档,也无法指望其绝对正确性。 该文档可能不清楚,不准确,可能会引起误解。 在大型团队或大型项目中,此问题可能变得非常严重。

尽管Python在项目的早期或中期阶段表现良好,但在某些时候,成功的项目和使用Python的公司可能会面临一个至关重要的问题:“我们是否需要用静态类型的语言重写所有内容?”

诸如mypy之类的类型检查系统通过为开发人员提供描述类型的正式语言,并检查类型描述与程序实现是否一致(并可选地检查它们的存在)来解决上述问题。 通常,我们可以说这些系统为我们提供了经过仔细检查的文档。

这样的系统的使用还有其他优点,它们已经完全不平凡:

  • 类型检查系统可以检测到一些小的(以及不是很小的)错误。 一个典型的例子是当他们忘记处理值None或某些其他特殊条件时。
  • 由于类型检查系统通常非常准确地报告需要更改的代码,因此大大简化了代码重构。 在这种情况下,我们不需要希望测试能100%覆盖代码,在任何情况下,这通常都是不可能的。 我们无需检查堆栈跟踪报告的深度即可找出问题的原因。
  • 即使在大型项目中,mypy通常也可以在瞬间进行完整类型检查。 测试的执行通常需要数十秒甚至几分钟。 类型检查系统为程序员提供即时反馈,并允许他更快地完成工作。 他不再需要编写易碎且需要大量支持的单元测试,而用mokas和patch代替真实的实体,只是为了更快地获得代码测试结果。

IDE和编辑器(例如PyCharm或Visual Studio Code)使用类型注释功能使开发人员能够自动完成代码,突出显示错误并支持常用的语言构造。 这些只是打字带来的一些优势。 对于某些程序员而言,所有这些都是支持键入的主要参数。 这是实施后立即受益的。 此类型用例不需要单独的类型检查系统,例如mypy,尽管应注意mypy有助于保持类型注释和代码之间的一致性。

Mypy背景


Mypy的故事始于英国的剑桥,比我加入Dropbox早了几年。 作为博士研究的一部分,我处理了静态类型化和动态语言的统一问题。 我的灵感来自有关杰里米·西克(Jeremy Siek)和瓦利德·塔哈(Walid Taha)的渐进式打字以及“打字式球拍”项目的文章。 我试图找到在各种项目中使用相同的编程语言的方法-从小型脚本到包含数百万行的代码库。 同时,我希望任何规模的项目都不必做出太大的妥协。 所有这些的重要部分是从无类型的原型项目逐步过渡到经过全面测试的静态类型的成品的想法。 如今,这些想法在很大程度上已被视为理所当然,但在2010年,这个问题仍在积极探索中。

我在类型检查领域的最初工作不是针对Python。 取而代之的是,我使用了少量的“自制”语言Alore 。 这是一个示例,可让您了解要解决的问题(类型注释在此处是可选的):

 def Fib(n as Int) as Int  if n <= 1    return n  else    return Fib(n - 1) + Fib(n - 2)  end end 

使用我们自己设计的简化语言是科学研究中常用的方法。 这不仅是因为它使您可以快速进行实验,还因为与研究无关的事实可以被随意忽略。 实际使用的编程语言通常是具有复杂实现的大规模现象,这减慢了实验速度。 但是,基于简化语言的任何结果看起来都有些可疑,因为当获得结果时,研究人员可能会牺牲对语言的实际使用很重要的考虑因素。

我用于Alore的类型检查工具看起来非常有前途,但是我想通过试验真实的代码来测试它,有人说这不是在Alore上编写的。 幸运的是,Alore主要基于与Python相同的思想。 重新构造类型检查工具非常简单,因此它可以与Python语法和语义一起使用。 这使我们能够尝试在开源Python代码中进行类型检查。 另外,我编写了一个运输工具,将用Alore编写的代码转换为Python代码,并使用它来翻译我的类型检查工具的代码。 现在,我有了一个用Python编写的类型检查系统,该系统支持Python的某种子集,某种语言! (某些对Alore有意义的架构解决方案不太适合Python,在mypy代码库的某些部分中仍然很明显。)

实际上,当时我的类型系统支持的语言还不能称为Python:由于Python 3类型注释语法的语法存在某些限制,因此它是Python的一种变体。

它看起来像Java和Python的混合体:

 int fib(int n):    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

当时我的想法之一是使用类型注释来通过在C或JVM字节码中编译这种Python来提高性能。 我进入编写编译器原型的阶段,但是我离开了这个想法,因为类型检查本身看起来非常有用。

最后,我在圣塔克拉拉(Santa Clara)的PyCon 2013会议上介绍了我的项目。 我还和慷慨的Python独裁者Guido van Rossum谈到了这一点。 他说服我放弃了自己的语法,并坚持使用标准的Python 3语法,Python 3支持函数注释,因此,可以如下所示重写我的示例,从而获得一个普通的Python程序:

 def fib(n: int) -> int:    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

我需要做出一些让步(首先,我想指出的是我出于这个原因发明了自己的语法)。 特别是当时的该语言的最新版本Python 3.3不支持变量注释。 我已经通过电子邮件与Guido讨论了将这些注释语法化的各种可能性。 我们决定对变量使用类型注释。 这使我们可以实现我们的目标,但是看起来有点麻烦(Python 3.6为我们提供了更舒适的语法):

 products = [] # type: List[str] # Eww 

类型注释对于Python 2支持也很方便,它缺少对类型注释的内置支持:

 f fib(n):    # type: (int) -> int    if n <= 1:        return n    else:        return fib(n - 1) + fib(n - 2) 

事实证明,这些(和其他)折衷实际上并没有多大意义-静态类型化的优点导致用户很快就忘记了不太完美的语法。 由于在控制类型的Python代码中没有使用特殊的语法构造,因此现有的Python工具和代码处理过程继续正常运行,这极大地促进了开发人员对新工具的开发。

在捍卫自己的毕业后,Guido还说服了我加入Dropbox。 这就是mypy历史中有趣的部分开始的地方。

待续...

亲爱的读者们! 如果您使用Python,请告诉我们您正在使用该语言开发的项目规模。


Source: https://habr.com/ru/post/zh-CN468233/


All Articles