geekdoc-python-zh/docs/pythonlibrary/type-checking-in-python.md

14 KiB
Raw Blame History

Python 中的类型检查

原文:https://www.blog.pythonlibrary.org/2020/04/15/type-checking-in-python/

类型检查或提示是 Python 3.5 中新增的一个新特性。类型提示也被称为类型注释。类型提示是向函数和变量声明中添加特殊的语法,告诉开发人员参数或变量是什么类型。

Python 不强制类型提示。因此,在 Python 中仍然可以随意更改类型。然而,一些集成开发环境,如 PyCharm支持类型提示并会突出显示输入错误。你也可以使用一个叫做 Mypy 的工具来帮你检查你的打字情况。在本文的后面,您将了解到关于该工具的更多信息。

您将了解以下内容:

  • 类型提示的利与弊
  • 内置类型提示/变量注释
  • 集合类型提示
  • 可以是None的暗示值
  • 类型提示功能
  • 当事情变得复杂时该怎么办
  • 班级
  • 装修工
  • 错认假频伪信号
  • 其他类型提示
  • 键入注释
  • 静态类型检查

我们开始吧!

类型提示的利与弊

谈到 Python 中的类型提示,有几件事情需要提前了解。让我们先来看看类型提示的优点:

  • 除了文档字符串之外,类型提示也是记录代码的好方法
  • 类型提示可以让 ide 和 linters 给出更好的反馈和更好的自动完成
  • 添加类型提示迫使您考虑类型,这可能有助于您在设计应用程序时做出正确的决策。

添加类型提示并不都是彩虹和玫瑰。有一些缺点:

  • 代码更加冗长,也更难编写
  • 类型提示增加了开发时间
  • 类型提示仅在 Python 3.5+中有效。在那之前,你必须使用类型注释
  • 类型提示在使用它的代码中会有一个较小的启动时间损失,特别是如果您导入了typing模块。

那么什么时候应该使用类型提示呢?以下是一些例子:

  • 如果您计划编写简短的代码片段或一次性脚本,则不需要包含类型提示。
  • 初学者在学习 Python 时也不需要添加类型提示
  • 如果您正在设计一个供其他开发人员使用的库,添加类型提示可能是一个好主意
  • 大型 Python 项目(即数千行代码)也可以从类型提示中受益
  • 如果你要写单元测试,一些核心开发人员建议添加类型提示

在 Python 中,类型提示是一个颇有争议的话题。您不需要一直使用它,但是在某些情况下,类型提示会有所帮助。

让我们用这篇文章的剩余部分来学习如何使用类型提示!

内置类型提示/变量注释

您可以使用下列内置类型添加类型提示:

  • int
  • float
  • bool
  • str
  • bytes

这些既可以用在函数中,也可以用在变量注释中。变量注释的概念是在 3.6 版本中添加到 Python 语言中的。变量批注允许您向变量添加类型提示。

以下是一些例子:

x: int  # a variable named x without initialization
y: float = 1.0  # a float variable, initialized to 1.0
z: bool = False
a: str = 'Hello type hinting'

您可以在根本不初始化变量的情况下向变量添加类型提示,第一行代码就是这种情况。另外 3 行代码展示了如何注释每个变量并适当地初始化它们。

接下来让我们看看如何为集合添加类型提示!

序列类型提示

在 Python 中,集合是一组项目。常见的集合或序列有listdicttupleset。但是,您不能使用这些内置类型来注释变量。相反,你必须使用typing模块。

让我们看几个例子:

>>> from typing import List
>>> names: List[str] = ['Mike']
>>> names
['Mike']

这里您创建了一个只有一个strlist。这表明您正在创建一个字符串的list。如果您知道列表总是相同的大小,您可以在列表中指定每个项目的类型:

>>> from typing import List
>>> names: List[str, str] = ['Mike', 'James']

提示元组非常相似:

>>> from typing import Tuple
>>> s: Tuple[int, float, str] = (5, 3.14, 'hello')

字典略有不同,因为您应该提示键和值的类型是:

>>> from typing import Dict
>>> d: Dict[str, int] = {'one': 1}

如果您知道集合的大小可变,您可以使用省略号:

>>> from typing import Tuple
>>> t: Tuple[int, ...] = (4, 5, 6)

现在让我们来学习如果一个项目是类型None该怎么做!

暗示值可能是零

有时一个值需要被初始化为None,但是当它被稍后设置时,你希望它是别的值。

为此,您可以使用Optional:

>>> from typing import Optional
>>> result: Optional[str] = my_function()

另一方面,如果值永远不能是None,那么您应该在代码中添加一个assert:

>>> assert result is not None

接下来让我们看看如何注释函数!

类型提示功能

类型提示函数类似于类型提示变量。主要的区别是你也可以给函数添加一个返回类型。

让我们来看一个例子:

def adder(x: int, y: int) -> None:
    print(f'The total of {x} + {y} = {x+y}')

这个例子向您展示了adder()有两个参数,xy,它们都应该是整数。返回类型是None,您可以在结束括号之后冒号之前使用->来指定。

假设您想将adder()函数赋给一个变量。您可以像这样将变量注释为Callable:

from typing import Callable

def adder(x: int, y: int) -> None:
    print(f'The total of {x} + {y} = {x+y}')

a: Callable[[int, int], None] = adder

Callable接受函数的参数列表。它还允许您指定返回类型。

让我们再看一个例子,在这个例子中,你可以传递更复杂的参数:

from typing import Tuple, Optional

def some_func(x: int, y: Tuple[str, str], 
              z: Optional[float]: = None): -> Optional[str]:
    if x > 10:
        return None
    return 'You called some_func'

对于这个例子,您创建了接受 3 个参数的some_func():

  • 一个int
  • 两个项目的字符串
  • 默认为None的可选float

注意,当你在函数中使用默认值时,在使用类型提示时,你应该在等号前后加一个空格。

它也返回None或一个字符串。

让我们继续前进,发现在更复杂的情况下该怎么做!

当事情变得复杂时该怎么办

你已经学会了当一个值可以是None时该怎么做,但是当事情变得复杂时你还能做什么?例如,如果传入的参数可以是多种不同的类型,您会怎么做?

对于特定用例,您可以使用Union:

>>> from typing import Union
>>> z: Union[str, int]

这个类型提示的意思是,变量z可以是字符串,也可以是整数。

也有函数接受一个对象的情况。如果该对象可以是几个不同对象中的一个,那么您可以使用Any

x: Any = some_function()

小心使用Any,因为你不能真正说出你要返回的是什么。由于它可以是“任何”类型,这就像用一个简单的except来捕捉所有异常。当你使用Any的时候,你不知道你在捕捉什么异常,你也不知道你在暗示什么类型。

班级

如果你已经写了一个class,你也可以为它创建一个注释。

>>> class Test:
...     pass
... 
>>> t: Test = Test()

如果您在函数或方法之间传递类的实例,这将非常有用。

装修工

装修工是一种特殊的野兽。它们是接受其他函数并修改它们的函数。在这本书的后面你会学到关于装饰者的知识。

给装饰者添加类型提示有点难看。

让我们来看看:

>>> from typing import Any, Callable, TypeVar, cast
>>> F = TypeVar('F', bound=Callable[..., Any])
>>> def my_decorator(func: F) -> F:
        def wrapper(*args, **kwds):
            print("Calling", func)
            return func(*args, **kwds)
        return cast(F, wrapper)

TypeVar是一种指定自定义类型的方式。您正在创建一个定制的Callable类型,它可以接受任意数量的参数并返回Any。然后创建一个装饰器,并添加新类型作为第一个参数的类型提示以及返回类型。

仅静态代码检查器实用程序 Mypy 使用了cast函数。它用于将值强制转换为指定的类型。在这种情况下,您将把wrapper函数转换为类型F

错认假频伪信号

您可以为类型创建新名称。例如,让我们将List类型重命名为Vector:

>>> from typing import List
>>> Vector = List[int]
>>> def some_function(a: Vector) -> None:
...     print(a)

现在VectorList指的是同一类型的提示。混淆类型提示对于复杂类型很有用。

typing文档中有一个很好的例子,复制如下:

from typing import Dict, Tuple

ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]

这段代码允许您将类型嵌套在其他类型中,同时仍然能够编写适当的类型提示。

其他类型提示

您还可以使用其他几种类型提示。例如,有一些通用的可变类型,比如MutableMapping,您可以将它们用于定制的可变字典。

还有一种ContextManager类型可以用于上下文管理器。

查看所有各种类型的所有细节的完整文档:

键入注释

Python 2.7 开发于 2020 年 1 月 1 日结束。然而,在未来的几年里,人们将不得不使用许多遗留的 Python 2 代码。Python 2 中从未添加类型提示。但是您可以使用类似的语法作为注释。

这里有一个例子:

def some_function(a):
    # type: str -> None
    print(a)

要实现这一点,您需要让注释以type:开头。这一行必须位于它所提示的代码的同一行或下一行。如果函数有多个参数,那么可以用逗号分隔提示:

def some_function(a, b, c):
    # type: (str, int, int) -> None
    print(a)

一些 Python IDEs 可能支持 docstring 中的类型提示。例如PyCharm 允许您执行以下操作:

def some_function(a, b):
    """
    @type a: int
    @type b: float
    """

Mypy 将处理其他评论,但不会处理这些评论。如果您使用 PyCharm您可以使用任何一种类型提示。

如果你的公司想使用类型提示,你应该提倡升级到 Python 3 来充分利用它。

静态类型检查

你已经多次看到有人提到我的 py。你可以在这里阅读所有相关内容:

如果您想在自己的代码上运行 Mypy您需要使用pip来安装它:

$ pip install mypy

一旦安装了mypy,就可以像这样运行这个工具:

$ mypy my_program.py

Mypy 将针对您的代码运行,并打印出它发现的任何类型错误。当 Mypy 运行时它不运行您的代码。这很像棉绒的工作原理。linter 是一个静态检查代码错误的工具。

如果你的程序中没有类型提示Mypy 将不会打印任何错误报告。

让我们编写一个错误类型的提示函数,并将其保存到一个名为bad_type_hinting.py的文件中:

# bad_type_hinting.py

def my_function(a: str, b: str) -> None:
    return a.keys() + b.keys()

现在您有了一些代码,您可以对它运行 Mypy:

$ mypy bad_type_hinting.py 
bad_type_hinting.py:4: error: "str" has no attribute "keys"
Found 1 error in 1 file (checked 1 source file)

这个输出告诉您第 4 行有一个问题。字符串没有keys()属性。

让我们更新代码,删除对不存在的keys()方法的调用。您可以将这些更改保存到名为bad_type_hinting2.py的新文件中:

# bad_type_hinting2.py

def my_function(a: str, b: str) -> None:
    return a + b

现在,您应该针对您的更改运行 Mypy看看您是否修复了它:

$ mypy bad_type_hinting2.py 
bad_type_hinting2.py:4: error: No return value expected
Found 1 error in 1 file (checked 1 source file)

哎呦!仍然有一个错误。这一次你知道你不期望这个函数返回任何东西。您可以修改代码,使其不返回任何内容,或者您可以修改类型提示,使其返回一个str

您应该尝试后一种方法,并将下面的代码保存到good_type_hinting.py:

# good_type_hinting.py

def my_function(a: str, b: str) -> str:
    return a + b

现在对这个新文件运行 Mypy:

$ mypy good_type_hinting.py 
Success: no issues found in 1 source file

这一次你的代码没有问题!

您可以针对多个文件甚至整个文件夹运行 Mypy。如果您致力于在代码中使用类型提示那么您应该经常在代码中运行 Mypy以确保您的代码没有错误。

包扎

您现在知道了什么是类型提示或注释,以及如何操作。事实上,您已经学习了有效进行类型提示所需的所有基础知识。

在本文中,您了解了:

  • 类型提示的利与弊
  • 内置类型提示/变量注释
  • 集合类型提示
  • 可以是None的暗示值
  • 类型提示功能
  • 当事情变得复杂时该怎么办
  • 班级
  • 装修工
  • 错认假频伪信号
  • 其他类型提示
  • 键入注释
  • 静态类型检查

如果遇到困难,您应该查看以下资源寻求帮助:

Python 中不需要类型提示。您可以编写所有代码,而无需在代码中添加任何注释。但是类型提示很好理解,并且可能证明在您的工具箱中有它很方便。