什么是Python 3.5中的类型提示

What are Type hints in Python 3.5

据说Python 3.5中提到的特性之一就是type hints

本文中提到了type hints的一个例子,同时也提到了负责任地使用类型提示。有人能解释得更多吗?什么时候应该使用,什么时候不应该?


我建议阅读PEP 483和PEP 484,并观看guido关于类型暗示的演示。好的。

简而言之:类型暗示就是字面上的意思,你暗示了你正在使用的对象的类型。好的。

由于python的动态特性,推断或检查正在使用的对象的类型尤其困难。这一事实使得开发人员很难理解他们尚未编写的代码中到底发生了什么,最重要的是,对于许多IDES中的类型检查工具[PyCharm,PyDev想到的是],这些工具由于没有任何对象类型的指示器而受到限制。因此,他们尝试推断类型(如演示中提到的)成功率约为50%。好的。

从提示式演示文稿中选取两张重要幻灯片:好的。为什么输入提示?

  • 帮助类型检查程序:通过提示您希望对象是什么类型,检查程序可以很容易地检测到,例如,您传递的对象的类型不是预期的。
  • 文档帮助:第三个人查看您的代码时,会知道在什么地方应该使用代码,因此,在不使用TypeErrors的情况下,如何使用它。
  • 帮助IDE开发更精确和更健壮的工具:当知道对象的类型时,开发环境将更适合于建议适当的方法。您可能在某个时候遇到过这样的情况:在某个IDE中,点击.并弹出没有为对象定义的方法/属性。
  • 为什么使用静态类型检查程序?

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

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

    您通常不需要担心它,也绝对不需要使用它(特别是在您将Python用作辅助脚本语言的情况下)。在开发大型项目时,它应该是有用的,因为它提供了非常需要的健壮性、控制和额外的调试功能。好的。用mypy提示:

    为了使这个答案更完整,我认为进行一点演示是合适的。我将使用mypy,这是一个在PEP中提供类型提示时激发类型提示的库。这本书主要是为任何碰到这个问题并想知道从哪里开始的人写的。好的。

    在此之前,让我重申以下几点:PEP 484不强制执行任何操作;它只是为函数设置一个方向。注释和建议如何执行类型检查的指南。您可以注释您的函数和提示尽可能多的东西;您的脚本仍然可以运行,而不考虑注释的存在,因为Python本身不使用它们。好的。

    无论如何,正如政治公众人物所指出的,暗示类型通常应采取三种形式:好的。

    • 函数注释。(PEP 3107)
    • 内置/用户模块的存根文件。
    • 补充前两种形式的特殊# type: type注释。(请参见:Python3.6中的变量注释是什么?对于用于# type: type注释的python 3.6更新)

    此外,您还需要将类型提示与Py3.5中引入的新typing模块结合使用。在它中,许多(附加的)abc(抽象基类)与用于静态检查的辅助函数和修饰器一起定义。大多数collections.abc中的ABCs都包含在Generic形式中,以允许订阅(通过定义__getitem__()方法)。好的。

    对于任何想更深入地解释这些问题的人来说,mypy documentation的编写都非常好,并且有大量的代码示例演示/描述其检查器的功能;这绝对值得一读。好的。功能注释和特殊注释:

    首先,在使用特殊注释时观察一些我们可以得到的行为是很有趣的。# type: type号特别意见可以在变量分配期间添加,以指示无法直接推断的对象类型。简单的作业是通常很容易推断,但其他的,如列表(关于其内容),则不能。好的。

    注:如果我们想要使用Containers的任何派生,并且需要指定容器的内容,我们必须使用typing模块中的通用类型。这些支持索引。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # 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一起运行,我们得到以下响应:好的。

    1
    2
    (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模块的额外类型):好的。

    1
    2
    def annotated(x: int, y: str) -> bool:
        return x < y

    annotated.__annotations__属性现在具有以下值:好的。

    1
    {'y': <class 'str'>, 'return': <class 'bool'>, 'x': <class 'int'>}

    如果我们是一个彻头彻尾的笨蛋,或者我们熟悉Py2.7的概念,因而不知道TypeError潜伏在annotated的比较中,我们可以执行另一个静态检查,捕捉错误并为我们节省一些麻烦:好的。

    1
    2
    3
    (Python3)jimmi@jim: mypy typeHintsCode.py
    typeFunction.py: note: In function"annotated":
    typeFunction.py:2: error: Unsupported operand types for > ("str" and"int")

    除此之外,使用无效参数调用函数也会被捕获:好的。

    1
    2
    3
    4
    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的模块中的三个随机函数中:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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类型的容器。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 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您应该逐渐开始看到更多的弹出窗口,其中一些在IDES(pycharm,)内部,另一些作为标准的python模块。我将尝试在下面的列表中添加更多的支票/相关包,如果我找到它们(或者如果建议)。好的。

    我知道的跳棋:好的。

    • MYPY:如本文所述。
    • Pytype:在谷歌,使用的符号和我收集到的不同,可能值得一看。

    相关包/项目:好的。

    • 排版:官方python repo为标准库提供各种存根文件。

    typeshed项目实际上是您可以查看如何在自己的项目中使用类型暗示的最佳位置之一。让我们以对应的.pyi文件中Counter类的__init__催款单为例:好的。

    1
    2
    3
    4
    5
    6
    7
    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:好的。

    A provisional package may have its API modified prior to"graduating" into a"stable" state. On one hand, this state provides the package with the benefits of being formally part of the Python distribution. On the other hand, the core development team explicitly states that no promises are made with regards to the the stability of the package's API, which may change for the next release. While it is considered an unlikely outcome, such packages may even be removed from the standard library without a deprecation period if the concerns regarding their API or maintenance prove well-founded.

    Ok.

    所以在这里拿点盐来;我怀疑它会被移除或以重要的方式改变,但人们永远不知道。好的。

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

    在python 3.6中看到什么是变量注释?如前所述,为这些小介绍。好的。好啊。


    在吉姆详细回答的基础上:

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

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

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

    typing模块还支持:

  • 键入别名。
  • 键入回调函数的提示。
  • 泛型-抽象基类已扩展为支持订阅,以表示容器元素的预期类型。
  • 用户定义的泛型类型-用户定义的类可以定义为泛型类。
  • 任何类型-每种类型都是任何类型的子类型。

  • 新发布的pycharm 5支持类型暗示。在他们关于它的博客文章中(参见pycharm 5中的python 3.5类型暗示),他们提供了一个关于什么是类型提示的很好的解释,并没有提供一些示例和插图来说明如何在代码中使用它们。

    另外,在python 2.7中也支持它,如本文中所述:

    PyCharm supports the typing module from PyPI for Python 2.7, Python 3.2-3.4. For 2.7 you have to put type hints in *.pyi stub files since function annotations were added in Python 3.0.