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

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


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

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

Due to the dynamic nature of Python, inferring or checking the type of an object being used is especially hard. This fact makes it hard for developers to understand what exactly is going on in code they haven't written and, most importantly, for type checking tools found in many IDEs (PyCharm and PyDev come to mind) that are limited due to the fact that they don't have any indicator of what type the objects are. As a result they resort to trying to infer the type with (as mentioned in the presentation) around 50% success rate.


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

为什么要输入提示?

Helps type checkers: By hinting at what type you want the object to be the type checker can easily detect if, for instance, you're passing an object with a type that isn't expected. Helps with documentation: A third person viewing your code will know what is expected where, ergo, how to use it without getting them TypeErrors. Helps IDEs develop more accurate and robust tools: Development Environments will be better suited at suggesting appropriate methods when know what type your object is. You have probably experienced this with some IDE at some point, hitting the . and having methods/attributes pop up which aren't defined for an object.

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

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

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

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


使用mypy类型提示:

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

在此之前,请允许我重申以下内容:PEP 484没有强制执行任何内容;它只是为函数设定一个方向 注释和建议如何执行类型检查的指导方针。可以对函数和进行注释 你想暗示多少就暗示多少;你的脚本仍然会运行,不管是否存在注释,因为Python本身并不使用它们。

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

函数注释(PEP 3107)。 内置/用户模块的存根文件。 特殊的# type:补充前两个表单的类型注释。(参见:什么是变量注释?Python 3.6更新# type: type注释)

此外,您还需要将类型提示与Py3.5中引入的新类型模块结合使用。在其中,定义了许多(额外的)abc(抽象基类)以及用于静态检查的辅助函数和装饰器。大部分abc集合。包括ABC,但为了允许订阅(通过定义__getitem__()方法),采用通用形式。

对于任何对这些有兴趣的更深入的解释,myypy文档写得非常好,有很多代码示例演示/描述他们的检查器的功能;这本书绝对值得一读。

函数注释和特殊注释:

首先,观察使用特殊注释时可以得到的一些行为是很有趣的。特殊# 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)只是打印 #类型注释被丢弃,被视为没有额外语义的普通注释。

另一方面,通过运行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类型,从静态角度讲,int类型是sound类型。这可以通过遵循a的类型并且只添加str对象来修复,或者通过改变a内容的类型来表明任何值都是可接受的(在输入any后直观地使用List[any]执行)。

函数注释以param_name: type形式添加在函数签名中的每个参数之后,返回类型使用->类型表示法在结束函数冒号之前指定;所有注释都以方便的字典形式存储在该函数的__annotations__属性中。使用一个简单的例子(它不需要从typing模块获取额外的类型):

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

带注释的。__annotations__属性现在有以下值:

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

如果我们是一个完全的新手,或者我们熟悉Python 2.7的概念,因此不知道TypeError隐藏在注释的比较中,我们可以执行另一个静态检查,捕捉错误并为我们节省一些麻烦:

(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或mypy文档将使您更全面地了解所提供的功能。

存根文件:

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

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

存根文件(扩展名为.pyi)是您正在制作/想要使用的模块的一个带注释的接口。它们包含 要用函数体进行类型检查的函数的签名被丢弃。让我们来感受一下,给定一组 在名为randfuncc .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模块。

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

我所知道的检查器:

我的:就像这里描述的那样。 PyType:通过谷歌,使用与我收集到的不同的符号,可能值得一看。

相关的包/项目:

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

类型项目实际上是您可以查看如何在自己的项目中使用类型提示的最佳场所之一。让我们以相应的.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类,我们可以看到它既可以在初始化器中不接受参数,也可以获得从任何类型到int的单个映射,或者接受任何类型的Iterable。


注意:我忘记提到的一件事是,键入模块是临时引入的。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.

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


**完全是另一个主题,但在类型提示的范围内有效: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存根文件。

在吉姆精心准备的回答中补充说:

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

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

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

类型模块还支持:

类型混叠。 回调函数的类型提示。 泛型——抽象基类已经扩展到支持订阅,以表示容器元素的预期类型。 用户定义的泛型类型——用户定义的类可以定义为泛型类。 Any类型——每个类型都是Any的子类型。

类型提示是为了可维护性,Python不会解释。在下面的代码中,def add(self, ic:int)行不会导致错误,直到下一次返回…线:

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#等静态类型检查语言一样)。

使用typehint, 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