Python 3.5中的类型提示是什么?

Python 3.5中被谈论最多的特性之一是类型提示

这篇文章这一个中提到了一个类型提示的例子,同时还提到了负责任地使用类型提示。谁能多解释一下它们,什么时候该用什么时候不该用?

159119 次浏览

我建议阅读PEP 483PEP 484,并在类型提示上观看这个演讲圭多

简单地说: 类型提示就是字面上的意思。提示您正在使用的对象的类型

由于Python的动态性质,使用对象的推断或检查类型尤其困难。这一事实使得开发人员很难理解他们还没有写的代码中到底发生了什么,最重要的是,对于许多ide (PyCharmPyDev中)中发现的类型检查工具来说,它们是有限的,因为它们没有任何对象类型的指示器。因此,他们只能以50%左右的成功率(如演示中所述)来推断类型。


从类型提示演示中选取两张重要的幻灯片:

为什么输入提示?< / >强

  1. 帮助类型检查:通过暗示你希望对象是什么类型,类型检查器可以很容易地检测到,例如,如果你传递的对象的类型不是预期的。
  2. 第三人查看你的代码将知道期望在哪里,因此,如何使用它,而不需要他们TypeErrors
  3. 帮助ide开发更准确、更健壮的工具:开发环境将更好地适合建议适当的方法,当知道你的对象是什么类型。你可能在某些IDE中遇到过这种情况,点击.,并弹出没有为对象定义的方法/属性。

为什么使用静态类型检查器?< / >强

  • 尽早发现bug:我相信这是不言而喻的。
  • 你的项目越大,就越需要它:同样,有意义。静态语言提供了健壮性和控制 动态语言缺乏。应用程序越大、越复杂,控制和可预测性就越强 行为方面)你需要。
  • 大型团队已经在运行静态分析:我猜这验证了前两点。

作为这个小介绍的结束语:这是一个可选特性,据我所知,它的引入是为了获得静态类型的一些好处。

你通常需要担心它,而肯定不需要使用它(特别是在你使用Python作为辅助脚本语言的情况下)。当开发像它提供了非常需要的健壮性、控制和额外的调试功能这样的大型项目时,它应该是有用的。


用mypy输入提示:

为了使这个答案更完整,我想做一个小小的演示是合适的。我将使用mypy,这个库启发了在PEP中显示的类型提示。这主要是为那些遇到这个问题,不知道从哪里开始的人写的。

在我这样做之前,让我重申以下内容:PEP 484不强制任何东西;它只是为函数设定一个方向 注释和提出如何类型检查的指导方针可以/应该执行。可以对函数和进行注释 你想暗示多少就暗示多少;不管是否存在注释,你的脚本仍然会运行,因为Python本身并不使用它们

无论如何,正如PEP中提到的,暗示类型通常应该采用三种形式:

此外,你还需要将类型提示与Py3.5中引入的新typing模块结合使用。其中,许多(额外的)abc(抽象基类)与辅助函数和装饰器一起定义,用于静态检查。collections.abc中的大多数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)就会打印出来 列表a的内容。# type注释已被丢弃,作为没有额外语义含义的普通注释处理.

. {/p> .

另一方面,通过运行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内容的类型来表明任何值都是可接受的(在Anytyping导入后直观地使用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'>}

如果我们是一个完全的新手,或者我们熟悉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模块

我将尝试在以下列表中添加额外的检查器/相关包,如果我找到它们(或如果建议)。

Checkers我知道的:

< >强相关的包/项目< / >强:

typeshed项目实际上是您可以查看如何在自己的项目中使用类型提示的最佳场所之一。让我们以对应的.pyi文件中的__ABC2类的__init__ dunders为例:

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


请注意:我忘记提到的一件事是typing模块已经在临时的基础上引入。从PEP 411:

临时包可以在“毕业”之前修改其API;变成“马厩”;状态。一方面,这种状态为包提供了正式成为Python发行版一部分的好处。另一方面,核心开发团队明确表示,对于包的API的稳定性没有任何承诺,这可能在下一个版本中发生变化。虽然这被认为是不太可能的结果,但如果对它们的API或维护的担忧被证明是有根据的,这些包甚至可以从标准库中删除,而不需要一个弃用期。

所以,对这里的事情持保留态度;我怀疑它会被删除或重大改变,但谁也不知道。


**完全是另一个主题,但在类型提示的范围内有效:PEP 526:变量注释语法是通过引入新语法来取代# type注释的努力,该语法允许用户在简单的varname: type语句中注释变量的类型。

如前所述,请参阅什么是变量注释?< / >,以获得对这些函数的简要介绍。

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

此外,在Python 2.7中也支持它,如这样的评论中所解释的:

PyCharm支持来自PyPI for Python 2.7, Python 3.2-3.4的类型模块。对于2.7,你必须在*中添加类型提示。自Python 3.0中添加函数注释以来,pyi存根文件

添加到吉姆详尽的回答:

检查typing模块——该模块支持由PEP 484指定的类型提示。

例如,下面的函数接受并返回类型为str的值,其注释如下:

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

typing模块还支持:

  1. 类型别名
  2. 回调函数的类型提示。
  3. 泛型 -抽象基类已经扩展到支持订阅,以表示容器元素的预期类型。
  4. 用户定义的泛型类型 -用户定义类可以定义为泛型类。
  5. 任何类型每个类型都是Any的子类型。

类型提示是为了可维护性,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所支持的基本变量类型的类型注释。它还包括一个打字库电池;这个类型库为我们提供了使用更特殊类型的方法。

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