实际上,现在我很好奇——如果你常常在python3中进行开拓,你是否在你的代码中利用类型注释/提示?
— Vicki Boykis (@vboykis) May 14, 2019
下面是利用了类型提示的代码的范例示例。

类型提示前的代码:
类型提示后的代码:
提示的样板格式常日是:
然而,对付它们是什么仍旧有很多让人困惑的地方(乃至它们的名称是什么——它们是提示还是注释?对付本文来说,我将把它们称为提示),以及它们如何有益于你的代码库。
当我开始调查和权衡类型提示是否适宜我利用时,我变得超级困惑。因此,就像我常日对我不理解的事情所做的那样,我决定深入挖掘,并希望这篇文章也将有助于其他人。
像往常一样,如果你看到某些内容并想进行评论,请随意提交pull要求。
打算机如何构建我们的代码要理解Python核心开拓职员在这里利用类型提示所做的事情,我们从Python中向下几个级别来深入理解,并更好地理解打算机和编程措辞的一样平常事情办法。
编程措辞的核心是利用CPU处理数据,并将输入和输出都存储在内存中的一种办法。
CPU是很蠢的。它可以做非常强大的事情,但它只懂机器措辞,而机器措辞的核心是电。机器措辞的一个表示形式是1和0。
为了得到那些1和0,我们须要从高等措辞转换到低级措辞。这便是编译措辞和解释措辞的用武之地。
当措辞被编译或实行时(python是通过阐明器实行的),代码被转换成低级的机器代码,见告打算机的低级组件,即硬件,要做什么。
有两种方法可以将代码转换成机器可读的代码: 你可以构建一个二进制文件并让编译器翻译它(c++、Go、Rust等),或者直接运行代码并让阐明器来翻译。后者是Python(以及PHP、Ruby和类似的“脚本”措辞)的事情事理。
硬件若何知道如何在内存中存储这些0和1 呢? 软件,我们的代码,须要见告它如何为这些数据分配内存。什么样的数据?这取决于措辞对数据类型的选择。
每种措辞都有数据类型。它们常日是你学习如何编程时最先学习的东西之一。
你可能会看到这样一篇教程(出自Allen Downey的精良著作——《像打算机科学家一样思考》),它讲述了数据类型是什么。大略地说,它们是表示内存中数据存放的不同办法。
数据类型有字符串、整数等等,这取决于你利用的是哪种措辞。例如,Python的基本数据类型包括:
还有由其他数据类型组成的数据类型。例如,Python列表可以包含整数、字符串或两者。
为了知道要分配多少内存,打算机须要知道存储的数据类型。幸运的是,Python有一个内置函数getsizeof,它会见告我们每种数据类型的大小(以字节为单位)。
这个奇妙的答案给了我们一些“空”数据构造的近似定义:
如果我们对它进行排序,我们可以看到默认情形下最大的数据构造是一个空字典,然后是一个凑集。整数和字符串比较就很小了。
这让我们知道程序中不同的类型占用多少内存。
我们为什么要在意?有些类型比其他类型更有效率,更适宜不同的任务。其他时候,我们须要对这些类型进行严格的检讨,以确保它们不会违反我们程序的某些假设。
但这些类型究竟是什么,我们为什么须要它们?
这便是类型系统发挥浸染的地方。
类型系统先容良久以前,在一个迢遥的星系里,人们手工做数学运算时创造,如果他们用“类型”来标记数字或方程的元素,他们就可以减少用数学证明这些元素时所碰着的逻辑问题。
由于在最初的打算机科学中,基本上是手工做大量的数学运算,一些事理被继续了下来,类型系统成为一种通过向特定类型分配不同的变量或元向来减少程序中bug数量的方法。
几个例子:
如果我们在为银行编写软件,那我们就不能将字符串放在打算某人账户总额的代码段中。如果我们正在处理调查数据并想知道某人是否做了某些事情,那选用布尔型Yes/No作为答案可能会将事情做到最好。在一个很大的搜索引擎中,我们必须限定许可儿们放入搜索框中的字符数,因此,我们要对某些特定类型的字符串进行类型校验。本日,在编程中,有两种不同的类型系统:静态系统和动态系统。Steve Klabnik将其阐明如下:
静态类型系统是一种机制,编译器通过这种机制检讨源代码并将标签(称为“类型”)分配给语法片段,然后利用它们来推断程序的行为。动态类型系统是一种机制,编译器通过这种机制天生代码来跟踪程序利用的数据种类(恰巧也称为“类型”)。
这是什么意思呢?这意味着,对付编译型措辞来说,你常日须要预先标记类型,以便编译器可以在程序编译时检讨它们,从而确保程序故意义。
这无疑是我最近读到的关于两者差异的最好阐明:
我过去利用过静态类型措辞,但过去几年的编程事情紧张是用Python编写的。一开始这种体验有点烦人,它让我以为它只是让我慢下来,迫使我变得非常刀切斧砍,而Python只是让我做我想做的,纵然我偶尔会出错。这有点像给一个总是打断你,并让你阐明清楚你的意思的人下指令与给一个总是点头附和,彷佛能理解你的人下指令比较,只管你并不愿定他们总是能听懂你说的话。
我花了一段韶光才理解这里的一个小警告:静态类型措辞和动态类型措辞是紧密联系的,但和编译型或阐明型措辞不是同义的。你可以对一个动态类型措辞(如Python)进行编译,也可以对静态措辞(如Java)进行阐明,例如,当你利用Java REPL时。
静态类型和动态类型措辞中的数据类型那么这两种措辞中的数据类型有什么不同呢?在静态类型中,必须预先设计好你的类型。例如,如果你在Java中事情,你会有这样一个程序:
如果你把稳到程序的开始部分,你会看到我们声明了一些变量,并带有一个这些类型是什么的指示符:
我们的方法还必须包含我们放入个中的变量,这样我们的代码才能精确编译。在Java中,你必须从头开始方案你的类型,以便编译器在将代码编译成机器码时知道它要检讨什么。
Python对用户隐蔽了这一点。类似的Python代码是:
这段代码底层是如何运行的呢?
Python如何处理数据类型?
Python是动态类型的,这意味当你运行程序时它只检讨你指定的变量的类型。正如我们在示例代码中看到的,你不必预先操持类型和内存分配。
处理的过程是:
在Python中,它会利用CPython将源代码编译成一种更大略的形式,称为字节码。这些指令在实质上类似于CPU指令,但是它们不是由CPU实行的,而是由称为虚拟机的软件实行的。(这些虚拟机不是仿照全体操作系统的VM,而只是一个简化的CPU运行环境。)
当CPython构建程序时,如果我们不指定变量的类型,它如何知道那些变量是什么类型?它不知道。它只知道变量是工具。Python中的所有东西都是一个工具,直到它不是(也便是说,当它变成了一个更详细的类型),我们才会详细地检讨它。
对付字符串之类的类型,Python会假设任何由单引号或双引号括起来的东西都是一个字符串。对付数字,Python会选择一个数字类型。如果我们试图对该类型做些什么,而Python不能实行该操作,它稍后会见告我们。
例如,如果我们试着:
它会见告我们它不能将一个字符串和一个浮点数相加。在知道name是一个字符串,seconds是一个浮点数之前,这段代码无法精确运行。
换句话说, Duck类型的涌现是由于当我们进行加法时,Python并不关心一个工具是什么类型。它所关心的是对它的加法方法的调用是否会返回任何合理的值。如果没有返回,系统会捕获一个缺点。
那么,这意味着什么呢?如果我们考试测验用与Java或C相同的方法编写程序,那么在CPython阐明器实行有问题的那一行之前,不会涌现任何缺点。
事实证明,对付处理较大型代码库的团队来说,这是未便利的,由于你不是在处理单个变量,而是处理相互调用的类之上的类,并且须要能够快速检讨所有内容。
如果你不能为它们编写良好的测试,并在运行于生产环境之前让它们捕捉到缺点,那么你可能会毁坏系统。
一样平常来说,利用类型提示有很多好处:
如果你正在处理繁芜的数据构造,或者具有大量输入的函数,那么在编写代码之后良久往后,你仍旧能够更随意马虎看到这些输入是什么。如果你有一个只带一个参数的函数,就像我们这里的例子,它是相称大略的。
但是,如果你正在处理一个包含大量输入的代码库,比如这个来自PyTorch文档的例子,又会若何呢?
模型是什么?啊,我们可以深入到代码库中,可以看到它是
但是如果我们可以在方法署名中定义它,那我们就不须要进行代码搜索,不是很酷吗?大概就像
device是若何的呢?
torch.device是什么?这是一种分外的PyTorch类型。如果我们查看文档和代码的其他部分,我们会创造:
如果我们能把稳到这一点,那我们就不用查找了,不是很好吗?
等等。因此,类型提示对写代码的人,也便是你,是很有帮助的。
类型提示对付其他人阅读你的代码也很有帮助。阅读别人已经标记好的代码要随意马虎得多,而不须要像上面那样进行搜索。键入提示可以增加可读性。
那么,Python做了什么来实现与静态类型措辞相同的可读性呢?
Python的类型提示这便是类型提示的浸染。作为补充解释,文档可以互换地将它们称为类型注释或类型提示。我将称其为类型提示。在其他措辞中,注释和提示的含义完备不同。
在Python2中,人们开始在他们的代码中添加提示来给出各种函数返回值的信息。
这样的代码最初看起来是这样的:
类型提示以前只是评论。但是,Python开始逐渐转向一种更统一的处理类型提示的方法,这些方法开始包括函数注释:
随着PEP484的发展,它与mypy被一起开拓出来,mypy是出自DropBox的一个项目,它会在你启动程序时检讨类型。请记住,在运行期间是不检讨类型的。只有当你考试测验在一个不兼容的类型上运行一个方法时,才会涌现问题。例如,考试测验对字典进行切片或考试测验从一个字符串中弹出值。
从实现细节来看,虽然这些注释在启动时通过普通的annotations属性可以利用,但在运行时不会进行类型检讨。相反,该建议假定存在一个独立的离线类型检讨器,用户可以自动在其源代码上运行。实质上,这样一个类型检讨器充当一个非常强大的linter。(当然,单个用户也可以在运行期间将类似的检讨器用于左券式设计和JIT优化,但这些工具还不足成熟。)
那么这在实际情形中是若何的呢?
类型提示还意味着你可以更随意马虎地利用IDE。例如,和VS Code一样,PyChamr也供应了基于类型的代码完成和检讨。
类型还有一个好处: 它能防止你犯屈曲的缺点。这是一个它若何阻挡犯错的很好的例子:
假设我们正在向一个字典添加名字
如果我们许可这种情形发生,我们的字典中就会有一堆格式禁绝确的条款。
我们如何办理这个问题?
通过对其运行mypy来办理:
我们可以看到mypy不许可这种类型。将mypy包含在一个管道中并在你的持续集成管道中进行测试是故意义的。
IDE中的类型提示利用类型提示的最大好处之一是,你可以在IDE中得到与利用静态类型措辞相同的自动完成功能。
例如,假设你有这样一段代码。这只是我们之前的两个函数,被封装在类中。
一个大略的事情是,现在我们已经(自由地)添加了类型,我们可以实际看一下当我们调用类方法时会发生什么:
开始利用类型提示
mypy文档中对付开始输入代码库有一些很好的建议:
要开始为你自己的代码编写类型提示,你须要理解以下几点:
首先,如果你利用的是字符串、整数、布尔型和基本的Python类型之外的任何类型,那么你就须要导入类型模块。
其次,通过该模块你可以得到几种繁芜的类型:Dict、Tuple、List、Set等等。
例如,Dict[str, float]表示你要检讨一个字典,个中键是字符串,值是一个浮点数。
还有一种类型称为Optional和Union。
第三,这是类型提示的格式:
如果你想进一步理解类型提示,许多聪明人已经编写了教程。
那么,结论是什么?用还是不用?
但是你该当开始利用类型提示吗?
这取决于你的用例。正如Guido和mypy文档所说的,
mypy的目的并不是要说服所有人都编写静态类型的Python—不管是现在还是将来,静态类型都是完备可选的。其目标是为Python程序员供应更多的选项,使Python在大型项目中成为比其它静态类型措辞更具竞争力的选择,从而提高程序员的事情效率和软件质量。
由于设置mypy和考虑所需类型的开销,类型提示对付较小的代码库和实验(例如,在Jupyter条记本中)来说没故意义。什么是小型代码库?守旧地说,可能是低于一千行的代码。
对付较大的代码库、与他人协作的地方和包、具有版本掌握和持续集成系统的地方来说,它是故意义的,可以节省大量韶光。
我的不雅观点是,在接下来的几年里,类型提示将会变得更加普遍,纵然不是很普遍的话,提前开始利用也没有坏处。
致谢
特殊感谢 Peter Baumgartner, Vincent Warmerdam, Tim Hopper, Jowanza Joseph, 和 Dan Boykis 对本文草稿的阅读。所有剩下的缺点都是我的 :)
英文原文:https://veekaybee.github.io/2019/07/08/python-type-hints/
译者:忧郁的红秋裤