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

What are type hints in Python 3.5?

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

类型提示 的示例在 this article and this one 中提到,同时还提到负责任地使用类型提示。有人可以解释更多关于它们的信息以及何时应该使用它们吗?

我建议阅读 PEP 483 and PEP 484 and watching this presentation by Guido 类型提示。

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

由于Python的动态性质,推断或检查正在使用的对象的类型特别困难.这一事实使开发人员很难理解他们未编写的代码中究竟发生了什么,最重要的是,对于在许多 IDE 中发现的类型检查工具(想到 PyCharm and PyDev)由于它们没有任何指示对象类型的事实,因此受到限制。因此,他们求助于尝试推断类型(如演示文稿中所述),成功率约为 50%。


从类型提示演示文稿中获取两张重要幻灯片:

为什么要输入提示?

  1. 帮助类型检查器:通过提示您希望对象成为什么类型,类型检查器可以轻松检测,例如,您是否正在传递一个类型为这不是预期的。
  2. 帮助文档:查看您的代码的第三人将知道预期的内容在哪里,因此,如何在不获取它们的情况下使用它TypeErrors
  3. 帮助 IDE 开发更准确、更强大的工具: 当知道您的对象是什么类型时,开发环境将更适合建议适当的方法。您可能在某些时候遇到过一些 IDE,点击 . 并弹出 methods/attributes,这不是为对象定义的。

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

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

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

您通常不需要担心它并且绝对不需要使用它(尤其是在您使用Python作为辅助脚本语言)。它在开发大型项目时应该很有帮助,因为 它提供了急需的稳健性、控制和额外的调试功能


用 mypy 类型提示:

为了让这个回答更完整,我觉得稍微演示一下比较合适。我将使用 mypy,该库启发了 PEP 中的类型提示。这主要是为遇到这个问题并想知道从哪里开始的任何人写的。

在此之前,让我重申以下几点:PEP 484 不强制执行任何内容;它只是为功能设置一个方向 如何类型检查can/should的注释和建议指南。您可以注释您的功能和 暗示尽可能多的事情;无论是否存在注释,您的脚本仍将 运行 因为 Python 本身不使用它们。

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

  • 函数注释(PEP 3107)。
  • built-in/user 个模块的存根文件。
  • 补充前两种形式的特殊 # type: type 评论。 (参见: Python 3.6 更新 # type: type 评论)

此外,您需要将类型提示与新的 typing module introduced in Py3.5. In it, many (additional) ABCs(抽象基础 classes)结合使用,这些提示与用于静态检查的辅助函数和装饰器一起定义。 collections.abc 中的大多数 ABC 都包含在内,但以 通用 形式以允许订阅(通过定义 __getitem__() 方法)。

对于任何对这些更深入的解释感兴趣的人,mypy documentation 写得非常好并且有很多代码示例 demonstrating/describing 他们的检查器的功能;绝对值得一读。

函数注释和特殊注释:

首先,观察我们在使用特殊注释时可以获得的一些行为是很有趣的。特别 # type: type 评论 可以在变量赋值期间添加以指示对象的类型,如果不能直接推断的话。简单的任务是 通常很容易推断,但其他人,如列表(就其内容而言)则不能。

注意: 如果我们想使用 containers 的任何派生物,并且需要指定容器的内容,我们 必须 使用 typing 模块中的 generic 类型。 这些支持索引。

# 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 的内容类型来表明任何值都是可接受的(直观地执行List[Any]typing 导入 Any 之后。

函数注释以 param_name : type 的形式添加在函数签名中的每个参数之后,并且在结束函数冒号之前使用 -> type 符号指定 return 类型;所有注释都以方便的字典形式存储在该函数的 __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 概念,因此不知道 TypeError 潜伏在 annotated 的比较中,我们可以执行另一个静态检查,捕获错误并为我们省去一些麻烦:

(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"

这些基本上可以扩展到任何用例,并且捕获的错误比基本调用和操作更广泛。你的类型 can check for 真的很灵活,我只是略微展示了它的潜力。查看 typing 模块, PEP 或 mypy 文档将使您更全面地了解所提供的功能。

存根文件:

存根文件可用于两种不同的非互斥情况:

  • 您需要对您不想直接更改函数签名的模块进行类型检查
  • 您想编写模块并进行类型检查,但还想将注释与内容分开。

存根文件(扩展名为 .pyi)是您要 making/want 使用的模块的注释接口。他们包含 要对函数的主体进行类型检查的函数的签名被丢弃。为了感受一下,给定一组 名为 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

combine 函数给出了为什么你可能想在不同的文件中使用注释的指示,它们有时会变得混乱 代码并降低可读性(Python 的大禁忌)。您当然可以使用类型别名,但有时会造成更多混淆 有帮助(所以明智地使用它们)。


这应该让您熟悉 Python 中类型提示的基本概念。即使使用的类型检查器已经 mypy 你应该会逐渐开始看到更多弹出窗口,一些在 IDEs (PyCharm,) 内部,而另一些作为标准 Python 模块。

我会尝试在以下列表中添加额外的 checkers/related 包,如果我找到它们(或如果建议的话)。

我知道的跳棋:

  • Mypy:如此处所述。
  • PyType:作者 Google,使用了与我收集到的不同的符号,可能值得一看。

相关Packages/Projects:

  • typeshed: 官方 Python 存储库包含标准库的各种存根文件。

typeshed 项目实际上是您可以了解如何在您自己的项目中使用类型提示的最佳场所之一。我们以对应的.pyi文件中的the __init__ dunders of the Counter class为例:

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: ...

Where _T = TypeVar('_T') is used to define generic classes。对于 Counter class 我们可以看到它可以在其初始化器中不带任何参数,从任何类型获取单个 Mappingint 或者 取一个 Iterable 任何类型。


注意:我忘记提及的一件事是typing模块是在临时的基础上引入的。来自 PEP 411:

A provisional package may have its API modified prior to "graduating" into a "stable" state. On one hand, this state provides the package with the benefits of being formally part of the Python distribution. On the other hand, the core development team explicitly states that no promises are made with regards to the the stability of the package's API, which may change for the next release. While it is considered an unlikely outcome, such packages may even be removed from the standard library without a deprecation period if the concerns regarding their API or maintenance prove well-founded.

所以对这里的事情持保留态度;我怀疑它是否会以重大方式被删除或更改,但永远不会知道。


** 完全是另一个主题,但在类型提示的范围内有效:PEP 526: Syntax for Variable Annotations 是通过引入新的注释来替换 # type 注释的努力允许用户在简单的 varname: type 语句中注释变量类型的语法。

参见,如前所述,对这些有一个小的介绍。

新发布的PyCharm5支持类型提示。在他们的博客 post 中(参见 Python 3.5 type hinting in PyCharm 5),他们对 类型提示是什么和不是 提供了很好的解释,并提供了几个示例和说明如何在您的代码中使用它们。

此外,它在 Python 2.7 中受支持,如 this comment 中所述:

PyCharm supports the typing module from PyPI for Python 2.7, Python 3.2-3.4. For 2.7 you have to put type hints in *.pyi stub files since function annotations were added in Python 3.0.

添加到

检查typing module -- this module supports type hints as specified by PEP 484

例如,下面的函数采用 returns 类型 str 的值,并注释如下:

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

typing模块还支持:

  1. Type aliasing.
  2. callback functions 的类型提示。
  3. Generics - 抽象基础 classes 已扩展为支持订阅以表示容器元素的预期类型。
  4. User-defined generic types - 用户定义的 class 可以定义为通用 class.
  5. Any type - 每个类型都是 Any 的子类型。

类型提示用于可维护性,不会被 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'