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

新手上路,请多包涵

Python 3.5 中最受关注的功能之一是 类型提示

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

原文由 Vaulstein 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 924
2 个回答

我建议阅读 PEP 483PEP 484 并观看 Guido 关于类型提示的 演讲

简而言之: _类型提示就是字面上的意思。您提示您正在使用的对象的类型_。

由于 Python 的 动态 特性, 推断或检查正在使用的对象的类型 特别困难。这一事实使开发人员很难理解他们未编写的代码中到底发生了什么,最重要的是,对于许多 IDE(想到 PyCharmPyDev )中发现的类型检查工具来说,由于以下事实而受到限制他们没有任何指示对象是什么类型的。因此,他们求助于尝试推断类型(如演示文稿中所述),成功率约为 50%。


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

为什么要输入提示?

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

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

  • 更快地找到错误:我相信这是不言而喻的。
  • 您的项目越大,您就越需要它:同样,这是有道理的。静态语言提供了动态语言所缺乏的健壮性和控制。您的应用程序越大越复杂,您就需要更多的控制和可预测性(从行为方面)。
  • 大型团队已经在运行静态分析:我猜这验证了前两点。

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

您通常 不需要 担心它,也 绝对 不需要使用它(尤其是在您使用 Python 作为辅助脚本语言的情况下)。它在开发大型项目时应该很有帮助,因为 _它提供了急需的稳健性、控制和额外的调试功能_。


使用 mypy 键入提示

为了使这个答案更完整,我认为稍微演示一下是合适的。我将使用 mypy ,该库启发了 PEP 中的类型提示。这主要是为遇到这个问题并想知道从哪里开始的任何人写的。

在我这样做之前,让我重申以下内容: PEP 484 不强制执行任何内容;它只是为函数注释设置了一个方向,并提出了如何/应该 如何 执行类型检查的指南。您可以注释您的函数并根据需要提示任意数量的内容;无论是否存在注释,您的脚本仍将运行,因为 Python 本身不使用它们。

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

  • 函数注释( PEP 3107 )。
  • 内置/用户模块的存根文件。
  • 特别 # type: type 补充前两种形式的评论。 (请参阅: 什么是变量注释? 对于 # type: type 注释的 Python 3.6 更新)

此外,您需要将类型提示与 typing 中引入的新模块 Py3.5 结合使用。其中,定义了许多(附加的) 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 注释已被丢弃, _被视为没有额外语义的普通注释_。

另一方面,通过使用 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]Anytyping 导入之后)。

函数注释以 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 模块。

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

_我知道的西洋跳棋_:

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

_相关包/项目_:

  • typeshed: 官方 Python 存储库,其中包含标准库的各种存根文件。

typeshed 项目实际上是您可以查看如何在您自己的项目中使用类型提示的最佳场所之一。让我们以对应的 .pyi 文件 中的 Counter 类的 __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 到 —3d9edf8072903ad5c8e4465c-348- int 取一个 Iterable 任何类型。


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

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

因此,请对这里的事情持保留态度;我怀疑它是否会以重大方式被删除或更改,但人们永远不会知道。


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

请参阅 什么是变量注释? ,如前所述,对这些进行小的介绍。

原文由 Dimitris Fasarakis Hilliard 发布,翻译遵循 CC BY-SA 4.0 许可协议

添加到 吉姆的详尽回答

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

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

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

typing 模块还支持:

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

原文由 Ani Menon 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题