探索Python中类型注释的深度。 第二部分

今天,我们将发布材料翻译的第二部分,该部分专门用于Python中的类型注释。



第一部分

Python如何支持数据类型?


Python是一种动态类型化的语言。 这意味着仅在程序执行期间检查使用的变量类型。 在本文上半部分给出的示例中,可以看到用Python编写的程序员无需计划变量的类型,也无需考虑需要多少内存来存储其数据。

当您准备要执行的Python代码时,将发生以下情况:“在Python中,使用CPython将源代码转换为一种更简单的形式,称为字节码。 字节码由本质上类似于处理器指令的指令组成。 但是它们不是由处理器执行的,而是由称为虚拟机的软件系统执行的。 (这不是关于虚拟机,其功能允许您在其上运行整个操作系统。在我们的情况下,这是一种环境,是处理器上运行的程序可用的环境的简化版本)。”

CPython在准备要执行的程序时如何知道应该是什么变量类型? 毕竟,我们没有指出这些类型。 CPython对此一无所知。 他只知道变量是对象。 Python中的所有东西都是一个对象 ,至少直到发现某些东西具有更特定的类型为止。

例如,Python将用单引号或双引号引起来的所有内容都视为字符串。 如果Python遇到数字,则认为相应的值是数字类型。 如果我们尝试对某个实体进行某种类型的实体无法完成的工作,Python将在稍后通知我们。

考虑当您尝试添加字符串和数字时出现的以下错误消息:

name = 'Vicki' seconds = 4.71; --------------------------------------------------------------------------- TypeError                 Traceback (most recent call last) <ipython-input-9-71805d305c0b> in <module>       3       4 ----> 5 name + seconds TypeError: must be str, not float 

该系统告诉我们无法添加字符串和浮点数。 而且, name是字符串, seconds是数字,这一事实直到尝试添加nameseconds才使系统不感兴趣。

换句话说,可以描述如下 :“执行加法时使用鸭子类型。 Python对特定对象具有哪种类型不感兴趣。 系统感兴趣的只是它是否返回对加法的有意义的调用。 如果不是这样,则会发出错误。

那是什么意思? 这意味着,如果我们使用Python编写程序,则在CPython解释器执行错误所在的同一行之前,我们不会收到错误消息。

这种方法在大型项目团队中使用时很不方便。 事实是,在此类项目中,它们不是使用单独的变量,而是使用复杂的数据结构。 在此类项目中,某些功能被其他功能调用,而这些功能又被其他功能调用。 团队成员应该能够快速检查其项目的代码。 如果他们无法编写好的测试来检测项目中的错误,然后再投入生产,则意味着此类项目可能会遇到很大的问题。

严格来说,我们来谈谈Python中的类型注释。

可以说,一般而言,使用类型注释具有许多优点 。 如果您使用需要很多输入值的复杂数据结构或函数,则使用批注可大大简化使用相似结构和函数的过程。 特别是-创建后的一段时间。 如果只有一个带有一个参数的函数(如此处给出的示例),那么使用这种函数在任何情况下都是非常简单的。

如果我们需要使用复杂的函数,而这些函数需要许多与PyTorch文档中的输入值相似的输入值,该怎么办

 def train(args, model, device, train_loader, optimizer, epoch):    model.train()    for batch_idx, (data, target) in enumerate(train_loader):        data, target = data.to(device), target.to(device)        optimizer.zero_grad()        output = model(data)        loss = F.nll_loss(output, target)        loss.backward()        optimizer.step()        if batch_idx % args.log_interval == 0:            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(                epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item())) 

什么是model ? 当然,我们可以深入研究代码库并找出:

 model = Net().to(device) 

但是,如果您只需在函数签名中指定model类型并避免不必要的代码分析,那就太好了。 也许看起来像这样:

 def train(args, model (type Net), device, train_loader, optimizer, epoch): 

device呢? 如果遍历代码,可以发现以下内容:

 device = torch.device("cuda" if use_cuda else "cpu") 

现在我们面临着什么是torch.device的问题。 这是PyTorch的一种特殊类型。 可以在PyTorch文档的相应部分找到其描述。

如果我们可以在函数的参数列表中指定类型device ,那就太好了。 因此,我们为那些不得不分析此代码的人节省了大量时间。

 def train(args, model (type Net), device (type torch.Device), train_loader, optimizer, epoch): 

这些考虑可以持续很长时间。

结果,事实证明类型注释对于编写代码的人非常有用。 但是,它们也使阅读其他人代码的人受益。 阅读类型化的代码比代码要理解要处理的实体要容易得多。 类型注释提高了代码的可读性。

那么,在Python中做了什么使代码达到与以静态类型的语言区分的代码相同的可读性呢?

Python中的类型注释


现在,我们准备好认真讨论Python中的类型注释。 当阅读用Python 2编写的程序时,可能会看到程序员为他们的代码提供了提示,告诉代码的读者这些函数返回的变量或值的类型。

类似的代码最初看起来像这样

 users = [] # type: List[UserID] examples = {} # type: Dict[str, Any] 

类型注释曾经是简单的注释。 但是碰巧的是,Python开始逐渐转向一种更加统一的注释处理方式。 特别是,我们正在谈论致力于功能注释的文档PEP 3107的出现。

接下来, PEP 484的工作开始了。 该文档专门用于类型注释,是与mypy(DropBox项目)紧密联系而开发的,该项目旨在在运行脚本之前验证类型。 使用mypy,值得记住的是在脚本执行过程中未执行类型检查。 例如,如果您尝试制作某种这种类型不支持的类型,则在运行时可能会收到错误消息。 说-如果您尝试对词典进行切片或为字符串调用.pop()方法。

您可以从PEP 484中了解有关注释实现的详细信息:“尽管这些注释可在运行时通过常规annotations属性使用,但在运行时不会执行类型检查。 相反,该建议提供了单独的独立类型检查工具的存在,用户可以根据需要使用该工具检查其程序的源代码。 通常,类似的类型检查工具的工作原理非常强大。 “当然,尽管个别用户可以使用类似的工具在运行时检查类型,无论是用于实施按合同设计的方法还是用于实施JIT优化。但是应该指出,此类工具尚未达到足够的成熟度。”

在实践中如何使用类型注释?

例如,它们的使用意味着可以促进各种IDE的工作。 因此, PyCharm基于类型信息提供代码完成和验证。 VS Code中提供了类似的功能。

类型注释之所以有用,还有另一个原因:它们可以保护开发人员免受愚蠢的错误的影响。 这是这种保护的一个很好的例子。

假设我们将人物姓名添加到字典中:

 names = {'Vicki': 'Boykis',         'Kim': 'Kardashian'} def append_name(dict, first_name, last_name):    dict[first_name] = last_name   append_name(names,'Kanye',9) 

如果我们允许这样做,词典中将有许多格式错误的条目。
让我们解决这个问题:

 from typing import Dict names_new: Dict[str, str] = {'Vicki': 'Boykis',                             'Kim': 'Kardashian'} def append_name(dic: Dict[str, str] , first_name: str, last_name: str):    dic[first_name] = last_name append_name(names_new,'Kanye',9.7) names_new 

现在与mypy一起检查此代码,并获得以下信息:

 (kanye) mbp-vboykis:types vboykis$ mypy kanye.py kanye.py:9: error: Argument 3 to "append_name" has incompatible type "float"; expected "str" 

可以看出,mypy不允许您使用需要字符串的数字。 建议那些希望定期使用此类测试的人将mypy包括在其持续集成系统中进行代码测试的地方。

在各种IDE中键入提示


使用类型注释的最重要的好处之一是,它们允许Python程序员在可用于静态类型语言的各种IDE中使用相同的代码完成功能。

例如,假设您有一段类似于以下内容的代码。 这是包装在类中的先前示例中的几个函数。

 from typing import Dict class rainfallRate:    def __init__(self, hours, inches):        self.hours= hours        self.inches = inches    def calculateRate(self, inches:int, hours:int) -> float:        return inches/hours rainfallRate.calculateRate() class addNametoDict:    def __init__(self, first_name, last_name):        self.first_name = first_name        self.last_name = last_name        self.dict = dict    def append_name(dict:Dict[str, str], first_name:str, last_name:str):        dict[first_name] = last_name addNametoDict.append_name() 

令人高兴的是,我们主动在代码中添加了类型描述,当调用类方法时,我们可以观察程序中发生了什么:



IDE类型提示

类型注释入门


您可以在mypy的文档中找到有关开始键入代码库时的入门的良好建议:

  1. 从小处开始-确保使用mypy验证包含多个批注的某些文件。
  2. 编写脚本以运行mypy。 这将有助于获得一致的测试结果。
  3. 在CI管道中运行mypy以防止类型错误。
  4. 逐步注释项目中最常用的模块。
  5. 将类型注释添加到您正在修改的现有代码中; 为他们提供您编写的新代码。
  6. 使用MonkeyType或PyAnnotate自动注释旧代码。

在开始注释自己的代码之前,对您进行一些处理将很有用。

首先,如果您使用字符串,整数,布尔值和其他基本Python类型的值以外的其他内容,则需要将类型输入模块导入代码中。

其次,该模块可以处理多种复杂类型。 其中包括DictTupleListSetDict[str, float]形式的构造意味着您想使用一个字典,该字典的元素使用字符串作为键,并使用浮点数作为值。 也有称为OptionalUnion类型。

第三,您需要熟悉类型注释的格式:

 import typing def some_function(variable: type) -> return_type:  do_something 

如果您想了解更多有关如何在项目中开始应用类型注释的信息,我想指出很多专门针对此的教程。 是其中之一。 我认为这是最好的。 掌握了它之后,您将了解代码注释及其验证。

结果。 在Python中使用类型注释是否值得?


现在,让我们问问自己是否应该在Python中使用类型注释。 实际上,这取决于项目的功能。 这是Guido van Rossum在mypy文档中关于此内容的说法:“ mypy的目的不是要说服所有人编写静态类型的Python代码。 静态类型现在和将来都是完全可选的。 Mypy的目标是为Python程序员提供更多选择。 这是为了使Python成为大型项目中使用的其他静态类型语言的更具竞争力的替代方案。 这是为了提高程序员的生产力并提高软件的质量。”

在小型项目和实验期间(例如,在Jupyter中进行的那些实验),配置mypy和计划某个程序所需的类型所需的时间并不能证明其合理性。 哪个项目应该算小? 根据仔细的估计,它的数量可能不超过1000行。

类型注释在较大的项目中有意义。 在那里,它们尤其可以节省大量时间。 我们正在谈论的是由一群程序员开发的项目,关于程序包,关于代码,这些都用于版本控制系统和CI管道的开发中。

我相信,在未来几年中,类型注释将比现在更加普遍,更不用说它们很可能会变成常规的日常工具。 我相信,一个在其他人之前开始与他们合作的人不会损失任何东西。

亲爱的读者们! 您是否在Python项目中使用类型注释?

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


All Articles