如何使用类型提示更好地编码(一)

164 阅读9分钟

这是关于Python中类型注释系统的系列文章的第一部分,简称类型提示。

通过这篇观点鲜明的文章,我提倡使用类型提示。我想解释一下为什么你应该关心,为什么你的代码会更好,更没有错误,更容易访问,而且更容易维护。最后,我将给你一些关于如何开始的建议。

类型提示是一个非常庞大的话题,快速浏览一下官方文档[4]就会感到有点迷茫。说实话,我自己对更高级的主题也不是很熟悉,比如说_通用_或_协议_!所以,如果你觉得不适应,不要担心。所以,如果你对这个话题的信息量感到不知所措,不要害怕,因为对我们来说,幸运的是,开始使用类型提示真的很容易,不需要很多知识。这篇文章针对的是类型提示的新手,希望能帮助你入门

在这第一部分中,我将对类型提示做一个简短的介绍,比较静态类型与动态类型这两个概念,并举例说明如何在 Python 中注释类型。

类型提示的简短介绍

每一种编程语言都必须有一些类型的概念,这样它才知道如何与对象一起工作,哪些操作是允许的,哪些是禁止的。

因此,即使没有任何明确的类型定义,例如,Python 也 "知道 "有可能添加不同种类的数字。

>>> type(1)
<class 'int'>

>>> type(2.2)
<class 'float'>

>>> 1 + 2.2
3.2

但不可能将一个字符串和一个数字相加。

>>> 1 + "2.2"
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Python 会通知你最后一个表达式是无效的,并在运行时引发一个TypeError 。这是有可能的,因为 Python 有一个类型系统,可以通知你不支持的操作。现在的问题是,这只在运行时发生,而不是在你发送代码之前发生!这就是Python是类型系统的后果。这就是 Python 是一种动态类型语言的后果。

动态类型化

Python 是一种动态类型的语言。这意味着两件事:Python 解释器只在代码运行时进行类型检查,并且变量的类型允许在其生命周期内改变

第一点意味着Python将不能在已经太晚之前发现像下面这样的问题错误。

if False:
  1 + "two"  # This line never runs, so no TypeError is raised at runtime, ever

在像 Java 这样的静态类型语言中,一旦你写下代码,编译器就会通知你这个有问题的TypeError 。然而在Python中,在这个问题实际发生之前,你不会被告知它。如果这个条件在将来被改变,并被评估为True ,那么程序除了在运行时没有任何问题外,还会突然引发一个TypeError所以在你的代码中可能有很多隐藏的类型错误,而你却没有意识到,因为它们在过去从未被触发过。

第二点也是一个值得关注的问题。一个变量可以任意改变其类型。

>>> thing = "Hello"
>>> type(thing)
<class 'str'>

>>> thing = 28.1
>>> type(thing)
<class 'float'>

在 Python 中,没有任何东西可以阻止一个变量改变它的类型。这往往正是我们想要的,也是我们青睐于Python的原因,因为它的简单性和易用性。然而,当一个曾经是x类型的变量突然改变为y类型,从而成为一个完全不同的对象时,这又可能导致你的程序出现问题。

对于按照作者的意图运行的代码来说,这可能不是一个大问题(比如一个命令行应用程序)。但是想想所有的程序,在这些程序中,用户通过提供输入或为他们自己的程序使用代码的组件与程序进行交互。我们没有办法知道你的代码会被外面的用户以多少种方式使用。由于在 Python 中没有办法保护对象变量和方法不被访问和改变,也没有办法保护对象不被子类化,所以你无法预见到可能的危险的类型变化。然而,有了类型提示,在正确的工具的帮助下,在运行前甚至在运行时确保和检查有效的类型成为可能,正如我将在本文的其余部分所展示的那样。

Python 中的类型提示

Python 将永远是一种动态类型的语言。然而,PEP 484[1]引入了类型提示,它为执行Python代码的静态类型检查铺平了道路。与大多数其它静态类型语言中的类型工作方式不同,类型提示本身并不会导致 Python 强制执行类型。正如它的名字所说,类型提示只是建议类型

所以让我们看一个如何在 Python 中使用类型提示的例子!

下面的函数通过添加适当的大写字母和装饰线将一个文本字符串变成一个标题。

def headline(text, title=True):
    if title:
      text = text.title()

    return f"{text}\n{'-' * len(text)}"

默认情况下,该函数返回标题的大写字母。通过设置title 标志为False ,你可以选择不加修改地打印标题文本。

>>> print(headline("My little headline"))
My Little Headline
------------------

>>> print(headline("My little headline", title=False))
My little headline
------------------

现在是我们的第一个类型提示的时候了!为了给函数添加类型信息,请对其参数和返回值做如下注释。

def headline(text: str, title: bool = True) -> str:
  # same code as before

这种新的语法告诉这段代码的读者,你希望参数text 是类型stringtitle 是类型bool 。此外,通过() -> str ,你又告知了该函数的返回类型,即string 。仅此一点,读者就能更清楚地了解这里发生了什么,以及你的函数做了什么。这个函数的标题接受一个文本和一个布尔值,最后再次返回一个文本。

你可能会说,这相当明显,但我不敢苟同。

首先,很容易使这个例子变得更加复杂,并提供一个有
更多代码
的函数
,你将很难真正找到使用每个函数属性的那一行,以了解它是如何被使用的以及它的类型是什么。有了类型提示,你只需阅读函数的定义就知道如何调用这个函数,因为你知道所有参数的所有类型。

其次,一般来说还有一个更微妙的问题:给变量命名。我特意选择了糟糕的参数名称title ,而不是更合适的名称,如titlecased (表示布尔性质)。然而,你并不总是能够找到一个能够成功地将变量的性质传达给读者的名字,随着代码越来越复杂,这也越来越难。而text 假设应该是一个string (但在你试过之前你能确定吗?),title 可能是很多东西,取决于你认为这个函数是做什么的。现在你可以继续阅读该函数的文档串,希望能得到一些关于这个参数的期望值的澄清,但老实说,可能没有文档串,或者至少没有非常丰富的内容。而将类型提示bool ,作为注解放在title 参数的后面,对每个人来说都是非常容易的事情。

第三,即使没有专门的工具来做静态类型检查,当你有一个编辑器或集成开发环境(IDE)在后台为你运行静态类型检查时,你可以从额外的类型提示中受益。我将在一分钟内回到这个问题上。

在这之前,我想让你清楚地了解一点,从而重复自己的观点:像这样添加类型提示没有运行时的效果。类型提示只是提示,本身并不强制执行。

让我们来演示一下这意味着什么:由于 Python 能够将任何表达式评估为TrueFalse ,我的函数存在一个真正的、难以察觉的错误,因为你可以用一个字符串 (或者任何其他的值,对于这个问题) 来调用函数,而不是布尔值,并得到这个结果。

>>>  print(headline("My first headline", "My Title"))
My First Headline
-----------------

尽管属性title 没有被设置为True ,但标题是标题大写的。这样做是因为Python将所有的字符串评估为True ,除非是空字符串"" ,这将被评估为False 。仅仅因为它今天有效,并不能保证它明天仍然有效!而且这显然不是函数作者的初衷。更重要的是,单从代码上看,你不可能总是知道函数作者的意图是什么,因为它可能是几种情况中的一种。类型提示使意图明确。而且你可能还记得 Python 的第二条禅。

明确的比隐含的好。

Python 之禅

本系列的第一部分到此结束。你应该已经了解了一般的类型提示,以及如何在 Python 中开始使用类型提示。在下一部分中,我将引导你通过一组实际使用类型提示的例子,为什么我认为类型提示在每种情况下都是有益的,以及如何对类型提示越来越熟悉。


我希望你喜欢阅读这篇文章!如果你有任何问题、评论或建议,不要犹豫,请通过PyBites Slack、 LinkedIn或 GitHub联系我你也可以在这个页面上直接留下你的评论。

如果你想阅读更多关于这个话题或任何其他话题,请告诉我。

保持冷静,用Python编码

资源

资源涵盖了关于类型提示的完整系列文章,所以如果这一部分没有引用所有的资源,请不要奇怪。

[1] PEP 484 - 类型提示

[2] PEP 483 - 类型提示的理论

[3] Python 类型检查 (指南) - Real Python

[4] 类型化模块文档

[6] 打字小抄 - Pysheeet

[7] Python中的Null。了解 Python 的 NoneType 对象 - Real Python