什么是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提示?
- 帮助types检查器:通过暗示你想要什么types的对象types检查器可以很容易地检测,例如,你是否传递了一个types不是预期的对象。
- 帮助文档:查看代码的第三人将知道什么是预期的地方,如何使用它,而不会得到
TypeErrors
。 - 帮助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__()
方法),它是以Generic
forms存在的。
对于对这些更深入解释感兴趣的人来说, 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
是有声的。 这可以通过遵守a
types并且仅仅追加str
对象或者通过改变a
的内容的types来解决,以指示任何值是可接受的(在从typing
导入List[Any]
之后直观地用List[Any]
执行) 。
函数注释以函数签名中的每个参数之后的param_name : type
forms添加,并且在结束函数冒号前使用-> 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的int
types。
# 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提示。
例如,下面的函数接受并返回str
types的值,并注释如下:
def greeting(name: str) -> str: return 'Hello ' + name
typing
模块还支持:
- 键入别名 。
- 键入提示callback函数 。
- generics – 抽象基类已经扩展为支持订阅以表示容器元素的预期types。
- 用户定义的genericstypes – 可以将用户定义的类定义为generics类。
- 任何types – 每种types都是Any的子types。