我建议阅读 PEP 483 和 PEP 484,并观看Guido关于类型提示的演示this presentation。
简而言之:类型提示字面意思是对你正在使用的对象的类型进行提示。
由于Python的动态性质,推断或检查正在使用的对象的类型尤其困难。这个事实使得开发人员难以理解他们没有编写的代码中到底发生了什么,最重要的是,许多IDE中的类型检查工具(例如PyCharm和PyDev)受限于它们没有任何指示对象类型的事实。因此,它们试图通过推断类型来解决问题(如演示中所提到的),成功率约为50%。从类型提示演示中选取两个重要的幻灯片:
TypeErrors
。.
键,然后弹出未为对象定义的方法/属性。作为这个小介绍的结束语:这是一个可选的功能,据我所知,它被引入是为了获得一些静态类型的好处。
通常情况下,你不需要担心它,绝对不需要使用它(特别是在你使用Python作为辅助脚本语言的情况下)。当开发大型项目时,它应该是有帮助的,因为它提供了非常需要的健壮性、控制力和额外的调试能力。
为了使这个答案更加完整,我认为需要进行一些演示。我将使用mypy
库,它启发了PEP中所呈现的类型提示。这主要是为那些遇到这个问题并想知道从何处开始的人编写的。
在我进行演示之前,让我再次重申以下内容:PEP 484并不强制执行任何内容;它只是为函数注释设置了一个方向,并提出了有关如何执行类型检查的指南。您可以为您的函数注释并提示尽可能多的内容;即使没有注释,您的脚本仍然会运行,因为Python本身不使用它们。
无论如何,正如PEP中所述,提示类型通常应采用三种形式:
# type: type
注释,补充前两种形式。 (参见:什么是变量注释? Python 3.6 的更新中有关 # type: type
注释)Py3.5
中引入的新typing
模块一起使用类型提示。在该模块中,定义了许多(额外的)ABCs(抽象基类),以及用于静态检查的辅助函数和装饰器。大多数collections.abc
中的ABC都被包含在其中,但以通用形式呈现,以便允许订阅(通过定义__getitem__()
方法)。mypy文档
写得非常好,并且有很多代码示例来演示/描述其检查器功能;它绝对值得一读。首先,观察使用特殊注释时我们可以得到的某些行为是很有趣的。可以在变量赋值期间添加特殊的 # 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)
只会打印列表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
的内容的类型来指示任何值是可接受的(在从typing
导入Any
之后以List[Any]
直观执行)。
函数注释以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'>}
(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
module, the
PEPs or the mypy
documentation will give you a more comprehensive idea of the capabilities offered..pyi
) are is an annotated interface of the module you are making/want to use. They contain
the signatures of the functions you want to type-check with the body of the functions discarded. To get a feel of this, given a set
of three random functions in a module named 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
< p > combine
函数说明了为什么您可能希望在不同的文件中使用注释,它们有时会使代码混乱并降低可读性(对于Python来说是大忌)。当然,您可以使用类型别名,但有时这会导致更多的困惑而不是帮助(因此要明智地使用它们)。
mypy
,但您应该逐渐开始看到更多的类型提示出现,一些在IDE内部(PyCharm),另一些作为标准Python模块。Counter
类的__init__
魔法方法为例(链接1)。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')
来定义通用类。对于 Counter
类,我们可以看到它可以在其初始化程序中不带任何参数,从任何类型获取单个 Mapping
到 int
或获取任何类型的 Iterable
。
所以要带着一点怀疑的态度看待这里的事情;我怀疑它会被删除或大幅修改,但人们永远无法知道。在毕业进入“稳定”状态之前,临时包的API可能会被修改。一方面,这种状态为包提供了成为Python发行版正式部分的好处。另一方面,核心开发团队明确表示,不对包的API稳定性做出承诺,其API可能会因下一个版本而改变。虽然这被认为是不太可能的结果,但如果关于其API或维护的担忧被证明是有根据的,这些包甚至可能在没有弃用期的情况下从标准库中删除。
**另一个完全不同的话题,但在类型提示范围内是有效的:PEP 526
: 变量注释语法是一项旨在通过引入新语法来替换# type
注释的工作,该语法允许用户在简单的varname: type
语句中注释变量的类型。
如先前提到的那样,请参阅什么是变量注释?以了解这些内容的简要介绍。
PyCharm支持来自PyPI的typing模块,适用于Python 2.7、Python 3.2-3.4。对于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'
Python 具有动态类型检查,因此类型在运行时而不是编译时(如 C# 等静态类型检查语言)中已知。
使用TypeHints,Python 支持对语言支持的基本变量类型 str、int、float、bool 和 None 进行类型注释。它还附带了一个 typing 库,这个 typing 库为我们提供了使用更多特殊类型的方法。
from typing import List
name: str = 'Tommy'
age: int = 24
height_in_meters: float = 1.7
阅读更多:https://tomisin.dev/blog/improving-your-python-projects-with-type-hints