Python 3.5 中最受关注的特性之一是类型提示。
本文和这篇文章中提到了类型提示的一个示例,同时还提到负责任地使用类型提示。有人可以解释更多关于它们以及何时应该使用它们以及何时不使用它们的信息吗?
我建议阅读PEP 483和PEP 484并观看Guido关于类型提示的演示文稿。
简而言之:类型提示就是字面意思。您提示您正在使用的对象的类型。
由于 Python 的动态特性,推断或检查正在使用的对象的类型特别困难。这一事实使开发人员很难理解他们未编写的代码中到底发生了什么,最重要的是,对于许多 IDE(想到PyCharm和PyDev )中发现的类型检查工具,由于以下事实而受到限制他们没有任何指示对象是什么类型的。因此,他们试图以大约 50% 的成功率(如演示文稿中所述)来推断类型。
从类型提示演示中获取两张重要的幻灯片:
TypeErrors
。.
并弹出未为对象定义的方法/属性。作为这个简短介绍的结束说明:这是一个可选功能,据我了解,它的引入是为了获得静态类型的一些好处。
您通常不需要担心它并且绝对不需要使用它(尤其是在您使用 Python 作为辅助脚本语言的情况下)。它在开发大型项目时应该很有帮助,因为它提供了急需的稳健性、控制和额外的调试功能。
为了使这个答案更完整,我认为进行一些演示是合适的。我将使用mypy
,该库激发了 PEP 中提供的类型提示。这主要是为遇到这个问题并想知道从哪里开始的任何人编写的。
在我这样做之前,让我重申以下几点:PEP 484不强制执行任何操作;它只是为函数注释设定一个方向,并就可以/应该如何执行类型检查提出指导方针。你可以注释你的函数并提示尽可能多的东西;无论是否存在注释,您的脚本仍将运行,因为 Python 本身不使用它们。
无论如何,正如 PEP 中所述,提示类型通常应采用三种形式:
# type: type
注释。(请参阅:什么是变量注释?有关 Python 3.6 更新的# type: type
评论)此外,您需要将类型提示typing
与Py3.5
. 其中,定义了许多(附加)ABC(抽象基类)以及用于静态检查的辅助函数和装饰器。大多数 ABCcollections.abc
都包含在内,但采用通用形式以允许订阅(通过定义__getitem__()
方法)。
对于任何有兴趣更深入解释这些的人,mypy documentation
写得非常好,并且有很多代码示例演示/描述其检查器的功能;绝对值得一读。
首先,观察我们在使用特殊注释时可以得到的一些行为是很有趣的。# 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)
只打印 list 的内容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]
after Any
has been imported from执行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 的概念,因此不知道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"
这些可以扩展到基本上任何用例,并且捕获的错误比基本调用和操作扩展得更远。您可以检查的类型非常灵活,我只是简要介绍了它的潜力。查看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 模块。
当我找到它们时(或如果建议),我将尝试在以下列表中添加其他检查器/相关包。
我知道的跳棋:
相关包/项目:
该typeshed
项目实际上是您可以查看如何在您自己的项目中使用类型提示的最佳场所之一。我们以对应文件中类的__init__
dundersCounter
为例:.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: ...
Where_T = TypeVar('_T')
用于定义泛型类。对于这个Counter
类,我们可以看到它既可以在其初始化程序中不接受任何参数,也可以Mapping
从任何类型获取单个到 anint
或接受Iterable
任何类型的 an。
注意:我忘记提及的一件事是该模块是临时typing
引入的。来自PEP 411:
临时包可能会在“毕业”进入“稳定”状态之前修改其 API。一方面,这种状态为包提供了正式成为 Python 发行版一部分的好处。另一方面,核心开发团队明确表示没有对包 API 的稳定性做出任何承诺,这可能会在下一个版本中发生变化。虽然这被认为是不太可能的结果,但如果对它们的 API 或维护的担忧被证明是有根据的,这些包甚至可以在没有弃用期的情况下从标准库中删除。
因此,请在此处加少许盐;我怀疑它是否会被删除或以重大方式改变,但人们永远无法知道。
**完全是另一个主题,但在类型提示范围内有效PEP 526
::变量注释的语法是通过引入新语法来替换注释的努力,# type
该语法允许用户在简单的varname: type
语句中注释变量的类型。
请参阅什么是变量注释?,如前所述,对这些进行简要介绍。
新发布的 PyCharm 5 支持类型提示。在他们关于它的博客文章中(请参阅PyCharm 5 中的 Python 3.5 类型提示),他们很好地解释了什么是类型提示,什么不是类型提示以及如何在代码中使用它们的几个示例和说明。
此外,它在 Python 2.7 中受支持,如此评论中所述:
PyCharm 支持 Python 2.7、Python 3.2-3.4 的 PyPI 输入模块。对于 2.7,您必须将类型提示放在 *.pyi 存根文件中,因为函数注释是在 Python 3.0 中添加的。
类型提示是为了可维护性,不会被 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'
类型提示是动态语言的最新补充,几十年来人们发誓命名约定就像匈牙利语一样简单(第一个字母 b = 布尔值、c = 字符、d = 字典、i = 整数、l = 列表、n = 数字的对象标签, s = string, t= tuple) 是不需要的,太麻烦了,但现在已经决定了,哦等等......使用语言 (type()) 来识别对象和我们花哨的 IDE 太麻烦了需要帮助来做任何复杂的事情,并且动态分配的对象值使它们无论如何都完全无用,而简单的命名约定可以解决所有这些问题,对于任何开发人员来说,一眼就能看出来。