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


250

类型提示是Python 3.5中讨论最多的功能之一。

的一个例子类型提示中提到的这篇文章这一个,同时还提负责任地使用类型提示。有人可以解释它们的更多信息,何时使用它们,何时不使用?


4
您应该看一下与官方变更日志链接的PEP 484
Stefan

1
@AvinashRaj:关于发布上了很好的讨论是怎么回事这里
Vaulstein

1
遗憾的是,此PEP 484完全忽略了C-API用例,尤其是Cython和Numba的类型提示。
denfromufa

Answers:


343

我建议阅读PEP 483PEP 484和观看演示由Guido的类型提示。

简而言之类型提示从字面上看是什么意思,您可以提示所使用的对象的类型

由于Python 的动态特性,推断或检查所使用对象的类型特别困难。这个事实使开发人员很难理解他们尚未编写的代码中到底发生了什么,而且最重要的是,对于许多IDE所使用的类型检查工具[PyCharm,PyDev想到],由于以下事实而受到限制:他们没有任何指示物是什么类型的指标。结果,他们求助于推断类型(如演示中所述),成功率约为50%。


摘录“类型提示”演示文稿中的两个重要幻灯片:

为什么要输入提示?

  1. 帮助类型检查器通过提示您希望对象成为哪种类型,例如,类型检查器可以轻松检测是否要传递的对象类型不是预期的类型。
  2. 帮助提供文档:查看您的代码的第三方将知道预期的用途,遍历,如何使用它而无需获取它们TypeErrors
  3. 帮助IDE开发更准确和健壮的工具:当知道对象的类型时,开发环境将更适合建议合适的方法。您可能曾经在某个IDE上遇到过这种情况,点击.并弹出未为对象定义的方法/属性。

为什么要使用静态类型检查器?

  • 尽早发现错误:我相信,这是不言而喻的。
  • 项目越大,就越需要:同样,这很有意义。静态语言提供了动态语言所缺乏的鲁棒性和控制力。您的应用程序越大,越复杂,就需要越多的控制和可预测性(从行为方面)。
  • 大型团队已经在进行静态分析:我猜这可以验证前两点。

作为这个小介绍的结束语:这是可选的功能,据我了解,已引入该功能是为了获得静态类型化的某些好处。

通常您无需担心它,并且绝对不需要使用它(尤其是在将Python用作辅助脚本语言的情况下)。在开发大型项目时,它应该会很有帮助,因为它提供了非常需要的鲁棒性,控制和其他调试功能


用mypy输入提示

为了使这个答案更完整,我认为稍作示范是合适的。我将使用mypy,它启发了PEP中介绍的Type Hints的库。这主要是为任何遇到此问题并想从哪里开始的人编写的。

在此之前,我要重申以下内容:PEP 484不执行任何操作;它只是为功能注释设置方向,并就如何 /应该执行类型检查。您可以注释功能,并根据需要提示任意多的内容。无论注释是否存在,您的脚本仍将运行,因为Python本身不使用它们。

无论如何,如PEP中所述,提示类型通常应采用三种形式:

此外,您将要结合使用类型提示和中typing引入的新模块Py3.5。其中定义了许多(附加)ABC(抽象基类)以及用于静态检查的辅助功能和装饰器。包含大多数内容ABCscollections.abcGeneric以允许订阅的形式(通过定义__getitem__()方法)。

对于那些对这些内容有更深入说明的人来说,mypy documentation它的编写非常好,并且有许多代码示例演示/描述了其检查器的功能。绝对值得一读。

功能注释和特殊注释:

首先,观察使用特殊注释时可以得到的某些行为很有趣。特别# type: type如果不能直接推断出对象的类型,则可以在变量分配期间添加注释以指示对象的类型。通常可以轻松推断出简单的任务,但是其他任务,例如列表(就其内容而言)则不能。

注意:如果要使用的任何派生形式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)仅打印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导入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'>}

如果我们是一个完整的noobie,或者我们熟悉Py2.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模块。当我发现它们(或建议)时,我将尝试在以下列表中添加其他检查器/相关程序包。

我知道的跳棋

  • Mypy:如此处所述。
  • PyType:由Google使用,与我收集到的符号不同,可能值得一看。

相关软件包/项目

  • 类型:官方Python回购,其中包含标准库的各种存根文件。

typeshed实际上,该项目是您可以查看的最佳场所之一,以了解如何在自己的项目中使用类型提示。让我们以相应文件中该类__init__笨拙Counter为例.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')用来定义泛型类。对于Counter该类,我们可以看到它可以在其初始值设定项中不接受任何参数,可以将Mapping任何类型的单个值获取为an,int 可以采用Iterable任何类型的。


注意:我忘记提及的一件事是该typing模块是临时引入的。从PEP 411

临时包可以在“升级”为“稳定”状态之前对其API进行修改。一方面,这种状态为软件包提供了正式加入Python发行版的好处。另一方面,核心开发团队明确声明,对于包API的稳定性没有任何保证,在下一发行版中可能会更改。虽然这被认为是不太可能的结果,但是如果对它们的API或维护的担心被证明是有根据的,那么甚至可以在不弃用期限的情况下从标准库中删除此类软件包。

所以在这里放些盐。我怀疑它将以重大方式被删除或更改,但是人们永远不会知道。


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

请参阅什么是Python 3.6中的变量注释?,如前所述,有关这些内容的小介绍。


3
“由于Python具有高度动态性,因此推断或检查所使用对象的类型特别困难。” 您是指静态检查,对吗?
bsam

53

添加到吉姆精心设计的答案中:

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

例如,下面的函数采用并返回type的值,str并且其注释如下:

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

typing模块还支持:

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

26

新发布的PyCharm 5支持类型提示。在有关它的博客文章中(请参阅PyCharm 5中的Python 3.5类型提示),他们提供了关于什么是类型提示和不存在类型提示的很好的解释,并提供了一些示例和插图说明如何在代码中使用它们。

此外,Python 2.7支持它,如以下注释中所述

PyCharm支持来自PyPI的用于Python 2.7,Python 3.2-3.4的键入模块。对于2.7,您必须在* .pyi存根文件中放入类型提示,因为函数注释是在Python 3.0中添加的


0

类型提示是对动态语言的最新补充,数十年来人们一直宣誓像匈牙利语一样简单的命名约定(对象标签的首字母为b =布尔语,c =字符,d =字典,i =整数,l =列表,n =数字,s =字符串,t =元组),太麻烦了,但是现在决定了,哦,等等……使用语言(type())来识别对象和我们花哨的IDE实在是太麻烦了需要帮助来完成任何复杂的事情,并且动态分配的对象值使它们无论如何都变得毫无用处,而对于任何开发人员而言,一个简单的命名约定就可以解决所有问题。


坦率地说,这听起来更像是一声喧than而不是答案。
Dimitris Fasarakis Hilliard

-1

类型提示是为了可维护性,不会被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'
 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.