Python 3.5中的类型提示是什么?

348

Python 3.5中最受关注的功能之一是类型提示

这篇文章这篇文章都提到了类型提示,并且提醒我们在使用时要谨慎。能否有人更详细地解释一下它们何时应该使用以及何时不应该使用?


4
你应该查看从官方变更日志链接到的PEP 484 - Stefan
1
@AvinashRaj:关于版本发布的讨论正在这里进行。 - Vaulstein
1
很遗憾,这个PEP 484完全忽略了C-API的使用情况,特别是对于Cython和Numba的类型提示。 - denfromufa
2
Python 3.6中的变量注释是什么? - Dimitris Fasarakis Hilliard
1
@bob 不会的,这在 PEP 484 中明确说明了。 - juanpa.arrivillaga
显示剩余2条评论
5个回答

449

我建议阅读 PEP 483PEP 484,并观看Guido关于类型提示的演示this presentation

简而言之类型提示字面意思是对你正在使用的对象的类型进行提示。

由于Python的动态性质,推断或检查正在使用的对象的类型尤其困难。这个事实使得开发人员难以理解他们没有编写的代码中到底发生了什么,最重要的是,许多IDE中的类型检查工具(例如PyCharmPyDev)受限于它们没有任何指示对象类型的事实。因此,它们试图通过推断类型来解决问题(如演示中所提到的),成功率约为50%。

从类型提示演示中选取两个重要的幻灯片:

为什么需要类型提示?

  1. 帮助类型检查器:通过提示对象应该是什么类型,类型检查器可以轻松检测到是否传递了一个不符合预期类型的对象。
  2. 有助于文档编写:查看您的代码的第三方将知道在哪里需要什么,因此可以使用它而不会出现TypeErrors
  3. 有助于IDE开发更准确、更强大的工具:当了解对象的类型时,开发环境将更适合建议适当的方法。您可能在某些IDE中经历过这种情况,按下.键,然后弹出未为对象定义的方法/属性。

为什么要使用静态类型检查器?

  • 更早地发现错误:这是不言而喻的,我相信。
  • 项目越大,你就越需要它:同样,很有道理。静态语言提供了动态语言所缺乏的健壮性和控制力。你的应用程序变得越来越大、越来越复杂,你就需要更多的控制和可预测性(从行为方面来看)。
  • 大型团队已经在运行静态分析:我猜这证实了前两点。

作为这个小介绍的结束语:这是一个可选的功能,据我所知,它被引入是为了获得一些静态类型的好处。

通常情况下,你不需要担心它,绝对不需要使用它(特别是在你使用Python作为辅助脚本语言的情况下)。当开发大型项目时,它应该是有帮助的,因为它提供了非常需要的健壮性、控制力和额外的调试能力。


使用mypy进行类型提示:

为了使这个答案更加完整,我认为需要进行一些演示。我将使用mypy库,它启发了PEP中所呈现的类型提示。这主要是为那些遇到这个问题并想知道从何处开始的人编写的。

在我进行演示之前,让我再次重申以下内容:PEP 484并不强制执行任何内容;它只是为函数注释设置了一个方向,并提出了有关如何执行类型检查的指南。您可以为您的函数注释并提示尽可能多的内容;即使没有注释,您的脚本仍然会运行,因为Python本身不使用它们。

无论如何,正如PEP中所述,提示类型通常应采用三种形式:

  • 函数注释 (PEP 3107)。
  • 内置/用户模块的存根文件。
  • 特殊的 # type: type 注释,补充前两种形式。 (参见:什么是变量注释? Python 3.6 的更新中有关 # type: type 注释)
此外,你需要与Py3.5中引入的新typing模块一起使用类型提示。在该模块中,定义了许多(额外的)ABCs(抽象基类),以及用于静态检查的辅助函数和装饰器。大多数collections.abc中的ABC都被包含在其中,但以通用形式呈现,以便允许订阅(通过定义__getitem__() 方法)。
对于任何对这些内容更深入解释感兴趣的人,mypy文档写得非常好,并且有很多代码示例来演示/描述其检查器功能;它绝对值得一读。
函数注释和特殊注释:

首先,观察使用特殊注释时我们可以得到的某些行为是很有趣的。可以在变量赋值期间添加特殊的 # type: type 注释,以指示对象的类型(如果无法直接推断)。简单赋值通常很容易推断,但其他情况,比如列表(关于它们的内容),则不能。

注意: 如果我们想要使用任何容器的衍生类,并且需要为该容器指定其内容,则必须使用typing模块中的通用类型。 这些支持索引。

# Generic List, supports indexing.
from typing import List

# In this case, the type is easily inferred as type: int.
i = 0

# Even though the type can be inferred as of type list
# there is no way to know the contents of this list.
# By using type: List[str] we indicate we want to use a list of strings.
a = []  # type: List[str]

# Appending an int to our list
# is statically not correct.
a.append(i)

# Appending a string is fine.
a.append("i")

print(a)  # [0, 'i']

如果我们将这些命令添加到一个文件中,并使用我们的解释器执行它们,一切都可以正常工作,print(a)只会打印列表a的内容。 # type注释已被丢弃,被视为普通注释,没有额外的语义含义
另一方面,通过使用mypy运行,我们会得到以下响应:
(Python3)jimmi@jim: mypy typeHintsCode.py
typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"

指出一个str对象的列表不能包含int,从静态角度来看是正确的。这可以通过遵守a的类型并仅附加str对象或通过更改a的内容的类型来指示任何值是可接受的(在从typing导入Any之后以List[Any]直观执行)。

函数注释以param_name:type的形式添加到函数签名中的每个参数后,并且使用-> type符号在结束函数冒号之前指定返回类型; 所有注释以便于字典形式存储在该函数的__annotations__属性中。使用一个简单的例子(不需要来自typing模块的额外类型):

def annotated(x: int, y: str) -> bool:
    return x < y

"

annotated.__annotations__ 属性现在具有以下值:

"
{'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

如果我们是完全的新手,或者熟悉Python 2.7的概念,因此不知道在比较“annotated”时潜藏的“TypeError”,我们可以执行另一个静态检查,捕获错误并节省麻烦:
(Python3)jimmi@jim: mypy typeHintsCode.py
typeFunction.py: note: In function "annotated":
typeFunction.py:2: error: Unsupported operand types for > ("str" and "int")

除其他外,使用无效参数调用函数也会被捕获:

annotated(20, 20)

# mypy complains:
typeHintsCode.py:4: error: Argument 2 to "annotated" has incompatible type "int"; expected "str"

These can be extended to basically any use case and the errors caught extend further than basic calls and operations. The types you can check for are really flexible and I have merely given a small sneak peak of its potential. A look in the typing module, the PEPs or the mypy documentation will give you a more comprehensive idea of the capabilities offered.
Stub files: Stub files can be used in two different non mutually exclusive cases: - You need to type check a module for which you do not want to directly alter the function signatures - You want to write modules and have type-checking but additionally want to separate annotations from content.
What stub files (with an extension of .pyi) are is an annotated interface of the module you are making/want to use. They contain the signatures of the functions you want to type-check with the body of the functions discarded. To get a feel of this, given a set of three random functions in a module named randfunc.py:
def message(s):
    print(s)

def alterContents(myIterable):
    return [i for i in myIterable if i % 2 == 0]

def combine(messageFunc, itFunc):
    messageFunc("Printing the Iterable")
    a = alterContents(range(1, 20))
    return set(a)

我们可以创建一个存根文件randfunc.pyi,在其中添加一些限制(如果我们希望这样做)。缺点是,没有存根的情况下查看源代码的人将无法真正获得注释帮助,从而理解应该在何处传递什么。
不管怎样,存根文件的结构非常简单:添加所有函数定义并填充空体(使用pass),并根据您的要求提供注释。在这里,让我们假设我们只想使用int类型来处理我们的容器。
# Stub for randfucn.py
from typing import Iterable, List, Set, Callable

def message(s: str) -> None: pass

def alterContents(myIterable: Iterable[int])-> List[int]: pass

def combine(
    messageFunc: Callable[[str], Any],
    itFunc: Callable[[Iterable[int]], List[int]]
)-> Set[int]: pass
< p > combine函数说明了为什么您可能希望在不同的文件中使用注释,它们有时会使代码混乱并降低可读性(对于Python来说是大忌)。当然,您可以使用类型别名,但有时这会导致更多的困惑而不是帮助(因此要明智地使用它们)。


这将使您熟悉Python中类型提示的基本概念。尽管使用的类型检查器是mypy,但您应该逐渐开始看到更多的类型提示出现,一些在IDE内部(PyCharm),另一些作为标准Python模块。
如果我发现其他检查器/相关软件包,我会在以下列表中添加它们(或者如果有建议)。
已知的检查器:
- Mypy:如此处所述。 - PyType:由Google开发,使用不同的符号表示法,可能值得一看。
相关软件包/项目:
- typeshed::官方Python存储库,包含各种标准库存根文件。
“typeshed”项目实际上是您可以查看如何在自己的项目中使用类型提示的最佳位置之一。让我们以相应的“.pyi”文件中Counter类的__init__魔法方法为例(链接1)
class Counter(Dict[_T, int], Generic[_T]):
        @overload
        def __init__(self) -> None: ...
        @overload
        def __init__(self, Mapping: Mapping[_T, int]) -> None: ...
        @overload
        def __init__(self, iterable: Iterable[_T]) -> None: ...

使用_T = TypeVar('_T')来定义通用类。对于 Counter 类,我们可以看到它可以在其初始化程序中不带任何参数,从任何类型获取单个 Mapping int 获取任何类型的 Iterable


注意:我忘了提到的一件事是,typing模块已经以“临时基础”引入。来自PEP 411

在毕业进入“稳定”状态之前,临时包的API可能会被修改。一方面,这种状态为包提供了成为Python发行版正式部分的好处。另一方面,核心开发团队明确表示,不对包的API稳定性做出承诺,其API可能会因下一个版本而改变。虽然这被认为是不太可能的结果,但如果关于其API或维护的担忧被证明是有根据的,这些包甚至可能在没有弃用期的情况下从标准库中删除。

所以要带着一点怀疑的态度看待这里的事情;我怀疑它会被删除或大幅修改,但人们永远无法知道。

**另一个完全不同的话题,但在类型提示范围内是有效的:PEP 526: 变量注释语法是一项旨在通过引入新语法来替换# type注释的工作,该语法允许用户在简单的varname: type语句中注释变量的类型。

如先前提到的那样,请参阅什么是变量注释?以了解这些内容的简要介绍。


4
由于Python语言的高度动态性,推断或检查正在使用的对象类型尤其困难。你是在指静态类型检查,对吗? - bsam
不知何故,PHP 引入了可选的 真实 类型提示,并且它运行得很好。例如,如果您对函数进行类型提示以返回字符串,并返回整数,则该整数将被转换为字符串。但对于大多数其他类型组合,会抛出错误。我希望 Python 处理类型时也能像这样。 - Robo Robok
那么,如果没有使用静态类型检查器(如Mypy),类型提示或存根文件是否毫无意义?(除了第三方可以轻松理解正在发生的事情) - starriet

68

27
新发布的PyCharm 5支持类型提示。在他们有关此功能的博客文章中(请参见Python 3.5在PyCharm 5中的类型提示),他们提供了一个很好的解释,说明< strong>类型提示是什么和不是什么,还提供了几个示例和插图,以说明如何在代码中使用它们。
另外,正如这篇评论所解释的那样,它也支持Python 2.7:

PyCharm支持来自PyPI的typing模块,适用于Python 2.7、Python 3.2-3.4。对于2.7版本,您需要将类型提示放入*.pyi存根文件中,因为函数注释是在Python 3.0中添加的。


3

类型提示有助于代码可维护性,但不会被Python解释。在下面的代码中,def add(self, ic:int) 的行直到下一个return... 行才会出现错误:

class C1:
    def __init__(self):
        self.idn = 1
    def add(self, ic: int):
        return self.idn + ic

c1 = C1()
c1.add(2)

c1.add(c1)

Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "<input>", line 5, in add
TypeError: unsupported operand type(s) for +: 'int' and 'C1'

0

Python 具有动态类型检查,因此类型在运行时而不是编译时(如 C# 等静态类型检查语言)中已知。

使用TypeHints,Python 支持对语言支持的基本变量类型 str、int、float、bool 和 None 进行类型注释。它还附带了一个 typing 库,这个 typing 库为我们提供了使用更多特殊类型的方法。

from typing import List

name: str = 'Tommy'
age: int = 24
height_in_meters: float = 1.7

阅读更多:https://tomisin.dev/blog/improving-your-python-projects-with-type-hints


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接