我建议阅读PEP 483 和PEP 484 并通过Guido 观看this presentation 的类型提示。
简而言之:类型提示就是字面意思。您提示您正在使用的对象的类型。
由于 Python 的动态 特性,推断或检查正在使用的对象的类型 尤其困难。这一事实使开发人员很难理解他们未编写的代码中到底发生了什么,最重要的是,对于许多 IDE 中的类型检查工具(想到PyCharm 和PyDev),这些工具是有限的他们没有任何指示对象是什么类型的事实。因此,他们试图以大约 50% 的成功率(如演示文稿中所述)来推断类型。
从类型提示演示中获取两张重要的幻灯片:
为什么要输入提示?
-
帮助类型检查器:通过提示您希望对象成为什么类型,类型检查器可以轻松地检测到,例如,您传递的对象的类型不是预期的。李>
-
帮助文档:查看您的代码的第三人将知道预期在哪里,因此,如何使用它而不需要他们
TypeErrors。
-
帮助 IDE 开发更准确、更强大的工具: 开发环境在知道您的对象是什么类型时,将更适合建议适当的方法。您可能曾经在某个 IDE 中遇到过这种情况,点击
. 并弹出未为对象定义的方法/属性。
为什么要使用静态类型检查器?
-
尽早发现错误:我相信这是不言而喻的。
-
您的项目越大,您就越需要它:再次强调,这是有道理的。静态语言提供了稳健性和控制能力,
缺乏动态语言。您的应用程序越大越复杂,可控性和可预测性就越高(从
行为方面)您需要。
-
大型团队已经在运行静态分析:我猜这验证了前两点。
作为这个简短介绍的结束说明:这是一个可选功能,据我了解,引入它是为了获得一些好处静态类型。
您通常不需要担心它并且绝对不需要使用它(尤其是在您使用 Python 作为辅助脚本语言的情况下)。它在开发大型项目时应该很有帮助,因为它提供了急需的稳健性、控制和额外的调试功能。
使用 mypy 进行类型提示:
为了让这个答案更完整,我觉得做个小示范比较合适。我将使用mypy,这个库激发了 PEP 中的类型提示。这主要是为遇到这个问题并想知道从哪里开始的任何人编写的。
在我这样做之前,让我重申以下几点:PEP 484 不强制执行任何操作;它只是为功能设定一个方向
可以/应该执行如何类型检查的注释和建议指南。您可以注释您的功能和
提示尽可能多的东西;无论注释是否存在,您的脚本仍将运行,因为 Python 本身不使用它们。
无论如何,如 PEP 中所述,提示类型通常应采用三种形式:
此外,您还需要将类型提示与Py3.5 中引入的新typing 模块结合使用。其中,许多(附加的)ABCs(抽象基类)与用于静态检查的辅助函数和装饰器一起定义。 collections.abc 中的大多数 ABC 都包含在内,但采用 通用 形式以便允许订阅(通过定义 __getitem__() 方法)。
对于任何有兴趣更深入解释这些的人,mypy documentation 写得非常好,并且有很多代码示例演示/描述其检查器的功能;绝对值得一读。
函数注解和特殊cmets:
首先,观察我们在使用特殊 cmets 时可以获得的一些行为是很有趣的。特价# type: typecmets
如果不能直接推断,可以在变量赋值期间添加以指示对象的类型。简单的任务是
通常很容易推断,但其他人,如列表(关于其内容),则不能。
注意:如果我们想使用 containers 的任何派生并且需要指定该容器的内容,我们必须使用 通用 类型来自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 cmets 已被丢弃,被视为没有额外语义意义的普通 cmets。
另一方面,通过使用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 的内容类型来指示任何值是可接受的(直观地在@987654367 之后使用List[Any] 执行)来解决@ 已从 typing 导入)。
函数注释以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"
这些可以扩展到基本上任何用例,并且捕获的错误比基本调用和操作扩展得更远。你的类型
can check for 真的很灵活,我只是简要介绍了它的潜力。查看typing 模块,
PEP 或mypy 文档将使您更全面地了解所提供的功能。
存根文件:
存根文件可用于两种不同的非互斥情况:
- 您需要对不想直接更改函数签名的模块进行类型检查
- 您希望编写模块并进行类型检查,但还希望将注释与内容分开。
存根文件(扩展名为.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)
我们可以创建一个存根文件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 您应该逐渐开始看到更多的弹出窗口,其中一些在 IDE 内部 (PyCharm,) 和其他标准 Python 模块。
如果我找到它们(或如果建议),我会尝试在以下列表中添加其他检查器/相关包。
我知道的跳棋:
-
Mypy: 如此处所述。
-
PyType:由 Google 提供,使用的符号与我收集的不同,可能值得一看。
相关包/项目:
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 类,我们可以看到它可以在其初始化程序中不接受任何参数,从任何类型获取单个Mapping 到int 或 获取任何类型的Iterable .
通知:我忘记提及的一件事是typing 模块已临时引入。来自 PEP 411:
临时包可能会在“毕业”进入“稳定”状态之前修改其 API。一方面,这种状态为包提供了正式成为 Python 发行版一部分的好处。另一方面,核心开发团队明确表示没有对包 API 的稳定性做出任何承诺,这可能会在下一个版本中发生变化。虽然这被认为是不太可能的结果,但如果对它们的 API 或维护的担忧被证明是有根据的,那么这些包甚至可以在没有弃用期的情况下从标准库中删除。
所以,这里要加点盐;我怀疑它是否会被删除或以重大方式改变,但我们永远无法知道。
** 完全是另一个主题,但在类型提示范围内有效:PEP 526: Syntax for Variable Annotations 旨在通过引入允许用户注释类型的新语法来替换 # type cmets简单的varname: type 语句中的变量。
如前所述,请参阅 What are variable annotations?,了解这些内容的简短介绍。