代码之家  ›  专栏  ›  技术社区  ›  Vaulstein MUGABA

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

  •  338
  • Vaulstein MUGABA  · 技术社区  · 9 年前

    Python 3.5中最受关注的特性之一是 类型提示 .

    以下示例 类型提示 在中提到 this article this one 同时还提到要负责任地使用类型提示。有人能解释更多关于它们的信息吗?什么时候应该使用,什么时候不使用?

    5 回复  |  直到 3 年前
        1
  •  0
  •   Tomisin Abiodun    2 年前

    我建议你读书 PEP 483 PEP 484 和观看 this presentation 通过 Guido 关于类型提示。

    简而言之 : 键入暗示是字面意思。提示您正在使用的对象的类型 .

    由于 动态 Python的本质, 推断或检查类型 所使用的对象的形状尤其困难。这一事实使得开发人员很难理解他们没有编写的代码中到底发生了什么,最重要的是,对于许多IDE中的类型检查工具来说( PyCharm PyDev 记住),因为它们没有任何对象类型的指示符。因此,他们倾向于尝试以大约50%的成功率(如演示中所述)推断类型。


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

    为什么键入提示?

    1. 帮助类型检查器: 通过暗示您希望对象是什么类型,类型检查器可以很容易地检测到,例如,您传递的对象的类型不是预期的。
    2. 文档帮助: 查看代码的第三方将知道预期的内容,即如何在不获取代码的情况下使用代码 TypeErrors .
    3. 帮助IDE开发更准确、更可靠的工具: 当知道对象的类型时,开发环境将更适合于建议适当的方法 . 并弹出未为对象定义的方法/属性。

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

    • 更快发现bug 我相信这是不言自明的。
    • 你的项目越大,你就越需要它 :再次,这是有道理的。静态语言提供了一种健壮性和控制能力 缺少动态语言。应用程序越大、越复杂,就越容易控制和预测(来自 行为方面)。
    • 大型团队已经在运行静态分析 我猜这验证了前两点。

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

    你通常 不要 需要担心 肯定 不需要使用它(尤其是在使用Python作为辅助脚本语言的情况下)。在开发大型项目时,它应该是有帮助的,因为 它提供了非常需要的鲁棒性、控制和额外的调试功能 .


    使用mypy键入提示 :

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

    在此之前,让我重申以下几点: 第484页 不执行任何操作;它只是为函数设置方向 注释和建议指南 怎样 可以/应该执行类型检查。您可以注释函数和 你想暗示多少就暗示多少;无论是否存在注释,脚本仍将运行,因为Python本身不使用它们。

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

    此外,您还需要将类型提示与新的 typing 中引入的模块 Py3.5 。其中,许多(附加) ABCs (抽象基类)与助手函数和修饰符一起定义,以用于静态检查。大多数ABC collections.abc 包含,但包含在 通用的 表单以允许订阅(通过定义 __getitem__() 方法)。

    对于任何有兴趣更深入地解释这些问题的人 mypy documentation 编写得非常好,有很多代码示例演示/描述了其检查器的功能;它绝对值得一读。

    函数注释和特殊注释:

    首先,观察我们在使用特殊评论时可以得到的一些行为是很有趣的。特殊的 #类型:类型 评论 可以在变量赋值期间添加,以指示对象的类型(如果无法直接推断)。简单作业是 通常很容易推断,但其他如列表(就其内容而言)则无法推断。

    注: 如果我们想使用 容器 并且需要指定该容器的内容 必须 使用 通用的 类型 打字 单元 这些支持索引。

    # 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 评论已被丢弃, 被视为没有附加语义的简单注释 .

    通过使用 我的 另一方面,我们得到以下响应:

    (Python3)jimmi@jim: mypy typeHintsCode.py
    typesInline.py:14: error: Argument 1 to "append" of "list" has incompatible type "int"; expected "str"
    

    表明 str 对象不能包含 int ,静态地说,这是声音。这可以通过遵守 仅附加 字符串 对象或通过更改 表示任何值都是可接受的(直观地用 List[Any] 之后 Any 已从导入 打字 ).

    函数注释添加到表单中 param_name : type 在函数签名中的每个参数之后,使用 -> type 结束函数冒号前的符号;所有注释都存储在 __annotations__ 属性 打字 模块):

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

    这个 annotated.__annotations__ 属性现在具有以下值:

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

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

    这些基本上可以扩展到任何用例,捕获的错误比基本调用和操作扩展得更远。您的类型 可以检查的是非常灵活的,我只是给出了它的潜力的一个小峰值。查看 打字 模块 PEP或 我的 文档将使您更全面地了解所提供的功能。

    存根文件:

    存根文件可以在两种不同的非互斥情况下使用:

    • 您需要对不想直接更改函数签名的模块进行类型检查
    • 您希望编写模块并进行类型检查,但还希望将注释与内容分开。

    哪些存根文件(扩展名为 .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 填充)和 根据您的需求提供注释。这里,假设我们只想与 整数 我们的集装箱类型。

    # 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中类型提示的基本概念。即使使用的类型检查器 我的 您应该逐渐开始看到更多的弹出窗口,其中一些在IDE内部( PyCharm ,)和其他作为标准Python模块。

    如果我找到了(或者如果建议),我会尝试在下面的列表中添加其他检查程序/相关包。

    我知道的跳棋 :

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

    相关包/项目 :

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

    这个 typeshed 项目实际上是您可以查看如何在自己的项目中使用类型提示的最佳场所之一。让我们举个例子 the __init__ dunders of the Counter class 在相应的 .复印件 文件:

    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: ...
    

    Where _T = TypeVar('_T') is used to define generic classes 。对于 柜台 类,我们可以看到它可以在初始值设定项中不接受任何参数 Mapping 从任何类型到 整数 采取行动 Iterable 任何类型的。


    注意 我忘了提一件事,那就是 打字 模块已在 临时基准 从…起 PEP 411 :

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

    所以在这里放点盐;我怀疑它是否会被删除或改变,但人们永远无法知道。


    ** 完全是另一个主题,但在类型提示的范围内有效: PEP 526 : Syntax for Variable Annotations 是一种努力 #类型 通过引入新语法,允许用户以简单的方式注释变量类型 varname: type 声明。

    看见 什么是变量注释? 如前所述,对这些进行了简单介绍。