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

自2014年以来,当Python引入对类型注释的支持时,程序员一直在代码中实现其实现。 该材料的作者是我们今天出版的翻译的第一部分,他说,根据她的评估,用Python 3编写的代码中约20-30%使用了非常粗体的现在类型注释(有时称为“提示”)。 这是调查结果,她于2019年5月发布在Twitter上。

事实证明,有29%的受访者使用注释。 根据文章的作者,近年来,她越来越多地在各种书籍和学习指南中遇到类型注释。

第二部分



在Python文档中,术语“类型提示”和“类型注释”可互换使用。 本文的作者主要使用术语“类型提示”,我们使用术语“类型注释”。

本文将涵盖有关在Python中使用类型注释的广泛问题。 如果要与作者讨论原始文章,可以使用请求请求机制。

引言


在这里,您可以找到有关使用类型注释编写的代码外观经典示例。

这是常规代码:

def greeting(name):     return 'Hello ' + name 

这是添加注释的代码:

 def greeting(name: str) -> str:    return 'Hello ' + name 

执行带有类型注释的代码所依据的模板如下所示:

 def function(variable: input_type) -> return_type:    pass 

乍看起来,在代码中应用类型注释看起来很简单。 但是,在了解确切的类型注释是什么时,开发人员社区仍然存在大量不确定性。 另外,在如何正确地称呼它们(“注释”或“提示”)以及在代码库中使用它们带来的好处方面甚至还有歧义。

当我开始研究该主题并考虑是否需要使用类型注释时,我感到完全困惑。 结果,我遇到了一些难以理解的事情,因此决定像往常一样做。 我决定深入研究这个问题,并以这种材料的形式进行我的研究,希望对像我一样想要用Python处理类型注释的那些人有用。

计算机如何执行我们的程序?


为了了解Python开发人员试图通过类型注释实现什么,让我们来谈谈位于Python代码以下几级的计算机系统的机制。 因此,我们可以更好地了解计算机和编程语言的总体工作方式。

编程语言的核心是工具,使您可以使用中央处理器(CPU)处理数据,以及将需要处理的数据和处理后的数据存储在内存中。


简化的计算机电路

从本质上讲,处理器是一件非常愚蠢的事情。 他能够用数据执行令人印象深刻的动作,但他只了解机器指令,这些指令归结为电信号集。 机器语言可以表示为由零和一组成。
为了准备这些零和处理器可以理解的零,您需要将代码从高级语言转换为低级语言。 这是编译器和解释器进入的地方。

如果该语言是已编译或可执行的 (Python代码通过解释程序执行),则该语言的代码将转换为包含用于低级计算机组件(即硬件)的指令的低级机器代码。

有几种方法可以将用某种编程语言编写的代码转换成机器可以理解的代码。 您可以使用程序代码创建文件,然后使用编译器将其转换为机器代码(这是C ++,Go,Rust和其他语言的工作方式),也可以直接使用解释器运行代码,后者负责将代码转换为机器命令。 这就是在解释器的帮助下,以Python以及其他“脚本”语言(例如PHP和Ruby)启动程序的方式。


口译语言代码处理方案

硬件如何知道如何在内存中存储零和一个表示程序使用的数据的数字? 我们的程序必须通知计算机如何为该数据分配内存。 这是什么数据? 这取决于特定语言支持的数据类型。

数据类型支持所有语言。 通常,数据类型是初学者学习特定语言的编程的首要主题之一。

例如,在同一Python上有出色的教程-在这里您可以找到有关数据类型的详细信息。 简而言之,数据类型是表示存储在内存中的数据的不同方式。

在现有的数据类型中,例如,可以指出字符串和整数。 开发人员可用的数据类型集取决于他们使用的编程语言。 例如,这里是基本Python数据类型的列表:

 int, float, complex str bytes tuple frozenset bool array bytearray list set dict 

存在由其他数据类型组成的数据类型。 例如,Python中的列表可以存储整数或字符串,以及两者。

为了找出需要分配多少内存来存储某些数据,计算机需要知道程序将在内存中放置哪种类型的数据。 Python具有内置的 getsizeof 函数 ,该函数将使我们知道存储各种数据类型的值所需的内存量(以字节表示)。

这是 StackOverflow的一个很好的答案,您可以在其中找到有关如何找出可存储在各种类型的变量中的“最小”值大小的信息。

 import sys import decimal import operator d = {"int": 0,    "float": 0.0,    "dict": dict(),    "set": set(),    "tuple": tuple(),    "list": list(),    "str": "a",    "unicode": u"a",    "decimal": decimal.Decimal(0),    "object": object(), } #   ,        d_size = {} for k, v in sorted(d.items()):    d_size[k]=sys.getsizeof(v) sorted_x = sorted(d_size.items(), key=lambda kv: kv[1]) sorted_x [('object', 16), ('float', 24), ('int', 24), ('tuple', 48), ('str', 50), ('unicode', 50), ('list', 64), ('decimal', 104), ('set', 224), ('dict', 240)] 

结果,通过对包含各种类型的值样本的字典进行排序,我们可以发现最大大小是一个空字典( dict ),然后是一个set( set )。 与它们相比,只需要很少的空间即可存储一个整数( int类型)。

上面的示例使我们了解了存储程序中使用的各种值需要多少内存。

为什么这要打扰我们呢? 事实是,某些类型在解决某些问题上比其他类型更好,从而使您可以更有效地解决这些问题。 在某些情况下,必须仔细检查类型。 例如,有时会检查程序中使用的数据类型是否与设计程序时所做的某些假设背道而驰。

但是这些类型是什么? 我们为什么需要它们? 这就是“类型系统”概念发挥作用的地方。

类型系统简介


很久以前 ,在遥远的星系中, 手动执行数学计算的人们意识到,如果将“类型”与方程式的数量或元素进行比较,则可以减少在获得有关这些元素的数学证明时出现的逻辑错误的数量。

由于从一开始就将计算机科学从本质上讲简化为执行大量的手动计算,因此一些旧原理被转移到了这些计算中。 类型系统已成为通过将适当的类型分配给各种变量或元素来减少程序中错误数量的工具。

以下是一些示例:

  • 如果我们为银行编写软件,则不能使用代码片段中的行来计算其他人帐户上的余额。
  • 如果我们使用的是调查数据,并且想了解某人对一个问题的肯定或否定回答,那么答案“是”和“否”将很自然地使用逻辑类型进行编码。
  • 在开发大型搜索引擎时,我们必须限制该系统的用户可以在搜索查询字段中输入的字符数。 这意味着我们需要检查一些字符串数据是否符合某些参数。

如今,在编程中,有两种主要的类型系统。 这是Steve Klabnik关于此问题的内容:“静态类型系统是编译器检查源代码并为程序片段分配标签(称为“类型”),然后使用它们得出有关程序行为的结论的机制。 动态类型系统是一种机制,编译器通过该机制生成代码,以观察程序使用什么类型的数据(巧合地也称为“类型”)。

这是什么意思? 这意味着使用编译语言时,通常需要预先分配实体类型。 因此,编译器将能够在代码编译期间对其进行检查,并从提供给它的源代码中找出是否有可能创建有意义的程序。

最近,我遇到了一种关于静态类型和动态类型之间区别的解释 。 这可能是我在该主题上阅读的最好的文字。 这是其中的一部分:“我曾经使用静态类型的语言,但是在过去的几年中,我主要使用Python进行编程。 一开始,使用静态输入使我有些恼火。 感觉到声明变量类型的需求变慢了,并迫使我过度表达自己的想法。 Python只是让我做自己想做的事情,即使我不小心做错了什么。 使用具有静态类型的语言就像将任务分配给总是再次询问的人,阐明要分配给他的案件的小细节。 动态打字是指被分配任务的人总是点头同意。 在这种情况下,有一种感觉,他了解您。 但是有时不能完全确定接受任务的人已经正确地知道了对他的期望。”

在谈论类型系统时,我遇到了一些我没有立即理解的东西。 即,“静态类型”和“动态类型”的概念与“编译语言”和“解释语言”的概念密切相关,但是术语“静态”和“编译”以及术语“动态”和“解释”不是同义词。 可以像Python一样动态地键入该语言,并同时对其进行编译。 类似地,语言可以是静态类型的,例如Java,也可以是解释型的(例如,对于Java,使用Java REPL时)。

静态和动态类型语言的数据类型比较


静态和动态类型语言中的数据类型之间有什么区别?

使用静态类型时,必须预先声明类型。 例如,如果您使用Java工作,则您的程序将如下所示:

 public class CreatingVariables {  public static void main(String[] args) {    int x, y, age, height;    double seconds, rainfall;    x = 10;    y = 400;    age = 39;    height = 63;    seconds = 4.71;    rainfall = 23;    double rate = calculateRainfallRate(seconds, rainfall);   } private static double calculateRainfallRate(double seconds, double rainfall) {  return rainfall/seconds; } 

注意程序的开始。 在那里声明了几个变量,接下来是这些变量类型的指示:

 int x, y, age, height; double seconds, rainfall; 

另外,在声明函数和声明其参数时都指定了类型。 没有这些类型声明,该程序将无法编译。 创建Java程序时,从一开始就需要计划这些或这些实体将具有的类型。 结果,编译器在处理此类程序的代码时,将知道在生成机器代码的过程中究竟需要检查什么。

Python消除了程序员的麻烦。 类似的Python代码可能如下所示:

 y = 400 age = 39 height = 63 seconds = 4.71 rainfall = 23 rate = calculateRainfall(seconds, rainfall) def calculateRainfall(seconds, rainfall):  return rainfall/seconds 

这一切如何在Python的肠道中起作用? 待续...

亲爱的读者们! 您使用哪种编程语言给人留下了最愉快的印象?

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


All Articles