什么是Python 3.5中的types提示

据说Python 3.5一个function被称为type hints

这篇文章提到了一个type hints的例子,同时也提到了负责任地使用types提示。 有人可以解释更多,什么时候应该使用,什么时候使用?

我build议阅读PEP 483和PEP 484,并观看Guido在Type Hinting上的演示。

简而言之, types提示实际上就是这个词的意思,你提示你正在使用的对象的types

由于Python的dynamic特性, 推断或检查正在使用的对象的types特别困难。 这个事实使得开发者很难理解他们没有编写的代码究竟是怎么回事,最重要的是,对于在许多IDE中find的types检查工具[PyCharm,PyDev想起来],由于这样的事实他们没有任何types的对象的指标。 因此,他们试图用(在演示中提到的)约50%的成功率来推断types。


从Type Hinting演示中抽取两张重要的幻灯片:

为什么input提示?

  1. 帮助types检查器通过暗示你想要什么types的对象types检查器可以很容易地检测,例如,你是否传递了一个types不是预期的对象。
  2. 帮助文档:查看代码的第三人将知道什么是预期的地方,如何使用它,而不会得到TypeErrors
  3. 帮助IDE开发更加准确和强大的工具:当知道什么types的对象时,开发环境将更适合于build议适当的方法。 您可能曾经在某个点上遇到过这种情况,点击了. 并popup方法/属性没有为一个对象定义。

为什么使用静态types跳棋?

  • 更快find错误 :我相信这是不言而喻的。
  • 你的项目越大,你需要的就越多 :再一次,这是有道理的。 静态语言提供了dynamic语言缺乏的健壮性和控制力。 您的应用程序变得越来越复杂,变得更加控制和可预测(从行为方面),您需要。
  • 大型团队已经在运行静态分析 :我猜这证实了前两点。

作为这个小介绍的结束语 :这是一个可选的function,据我所知,它已被引入,以获得静态types的一些好处。

您通常不需要担心它, 绝对不需要使用它(特别是在您使用Python作为辅助脚本语言的情况下)。 在开发大型项目时应该有所帮助,因为它提供了强大的稳健性,控制和其他debuggingfunction


用mypy打字

为了使这个答案更完整,我想有一点示范是适合的。 我将使用mypy ,这是启发types提示的库,因为它们在PEP中呈现。 这主要是写给任何人碰到这个问题,想知道从哪里开始。

在我这样做之前,让我重申以下几点: PEP 484没有强制执行; 它只是为函数注释设定一个方向,并提出如何进行types检查的指导方针。 你可以注释你的function,并提示你想要的东西; 无论注释是否存在,脚本仍然会运行,因为Python本身不使用它们。

无论如何,正如PEP所指出的那样,暗示types通常应该有三种forms:

  • function注释。 ( PEP 3107 )
  • 内置/用户模块的存根文件。
  • 特别# type: type注释,补充前两种forms。 (请参阅: Python 3.6中的variables注释是什么?针对#type的Python 3.6更新# type: type注释)

另外,你会希望将types提示与Py3.5引入的新的typing模块结合使用。 其中,许多(附加)ABCs(抽象基类)与辅助函数和装饰器一起定义,用于静态检查。 collections.abc中的大多数ABCs都包含在其中,但为了允许订阅(通过定义__getitem__()方法),它是以Genericforms存在的。

对于对这些更深入解释感兴趣的人来说, mypy documentation写得非常好,并且有很多代码示例演示/描述了他们的检查器的function; 这绝对值得一读。

function注释和特别注释:

首先,观察一些我们在使用特殊评论时可以得到的行为是很有趣的。 特殊# type: type可以在variables赋值期间添加# type: type注释,以指示如果不能直接推断的对象的types。 简单的作业通常很容易被推断出来,但其他的,如列表(关于他们的内容)不能。

注意:如果我们想使用Containers派生类,并且需要指定那个容器的内容,我们必须使用来自typing模块的通用types。 这些支持索引。

 # 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 ,静态地说,这个int是有声的。 这可以通过遵守atypes并且仅仅追加str对象或者通过改变a的内容的types来解决,以指示任何值是可接受的(在从typing导入List[Any]之后直观地用List[Any]执行) 。

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

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

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

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

如果我们是一个完整的noobie,或者我们熟悉Py2.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" 

这些可以扩展到基本上任何用例,并且捕获的错误比基本的调用和操作延伸得更远。 你可以检查的types是非常灵活的,我只是给了它潜力的一个小的潜行高峰。 typing模块,PEP或mypy文档的mypy可以让您更全面地了解所提供的function。

存根文件:

存根文件可以用于两个不相互排斥的情况:

  • 您需要键入检查您不希望直接更改function签名的模块
  • 你想写模块,并进行types检查,但另外要分开注释和内容。

哪些存根文件(扩展名为.pyi )是您正在使用/想要使用的模块的注释接口。 它们包含您想要键入的函数的签名 – 请检查放弃的函数的主体。 为了得到这个感觉,给定一个名为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) 

我们可以创build一个存根文件randfunc.pyi ,如果我们希望的话,我们可以放置一些限制。 不利之处在于,有人在没有存根的情况下查看源代码时,在试图理解应该传递到哪里的时候,并不会真正获得注释的帮助。

无论如何,存根文件的结构非常简单:添加所有具有空体的函数定义( pass填充)并根据您的要求提供注释。 在这里,我们假设我们只想使用Containertypes的inttypes。

 # 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来说,这是大不了的)。 你当然可以使用types别名,但有时混淆不止有用(所以明智地使用它们)。


这应该让你熟悉Python中Type Hints的基本概念。 即使使用的types检查程序是mypy你应该逐渐开始看到更多的popup窗口,一些内部的IDE( PyCharm )和其他作为标准的Python模块。 我会尝试添加额外的检查/相关包在下面的列表,如果我find他们(或如果build议)。

跳棋我知道

  • Mypy :如此处所述。
  • PyType :Google使用不同的符号,可能值得一看。

相关软件包/项目

  • types化:官方Python回购包含标准库的各种存根文件。

这个typeshed项目实际上是你可以看到的最好的地方之一,你可以看看如何在你自己的项目中使用types提示。 我们以相应的.pyi文件中的Counter类的__init__ dunders .pyi

 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')用于定义generics类 。 对于Counter类,我们可以看到它可以在其初始化程序中不带任何参数,从任何types获取单个Mapping到一个int 或者获取任何types的Iterable


注意 :我忘了提到的一件事是typing模块是暂时引入的。 从PEP 411

临时包可能会在“gradle”之前将其API修改为“稳定”状态。 一方面,这个状态为程序包提供了正式成为Python发行版的一部分的好处。 另一方面,核心开发团队明确表示,对于包的API的稳定性没有任何承诺,下一版可能会有所变化。 虽然这被认为是一个不太可能的结果,但如果对API或维护的担忧有充分根据的话,甚至可以将这些软件包从标准库中删除,而不需要贬低期。

所以拿一点盐来拿东西 我怀疑它会被重大的删除或改变,但永远不会知道。


**另一个话题,但在types提示范围内是有效的: PEP 526 :variables注释的语法是通过引入新的语法来replace# type注释,它允许用户在简单的varname: type语句中注释variables的varname: type

请参阅Python 3.6中的variables注释是什么? ,如前所述,在这些小介绍。

新发布的PyCharm 5支持types提示。 在他们的博客文章中(请参阅PyCharm 5中的Python 3.5types提示 ),它们提供了关于什么types提示的一个很好的解释,以及如何在代码中使用它们的例子和插图。

另外,它在Python 2.7中得到了支持,正如在这个评论中所解释的那样:

PyCharm支持PyPI for Python 2.7,Python 3.2-3.4的打字模块。 对于2.7,你必须把types提示放在* .pyi存根文件中,因为函数注释是在Python 3.0中添加的

加上Jim精心devise的答案:

检查typing模块 – 该模块支持PEP 484指定的types提示。

例如,下面的函数接受并返回strtypes的值,并注释如下:

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

typing模块还支持:

  1. 键入别名 。
  2. 键入提示callback函数 。
  3. generics – 抽象基类已经扩展为支持订阅以表示容器元素的预期types。
  4. 用户定义的genericstypes – 可以将用户定义的类定义为generics类。
  5. 任何types – 每种types都是Any的子types。