Python 3.5中最受关注的特性之一是type hints.

this articlethis one中提到了type hints的例子,同时也提到了负责任地使用类型提示.有人能详细解释一下它们,什么时候应该使用,什么时候不应该使用吗?

推荐答案

我建议你读PEP 483PEP 484,在打字提示上看this presentationGuido.

In a nutshell: Type hinting is literally what the words mean. You hint the type of the object(s) you're using.

由于Python的dynamic特性,正在使用的对象的inferring or checking the type特别困难.这一事实使得开发人员很难理解他们没有编写的代码中到底发生了什么,最重要的是,对于在许多IDE(想到PyCharmPyDev)中找到的类型判断工具来说,由于没有任何关于对象类型的指示符,这些工具是有限的.因此,他们求助于try 推断类型(如演示中提到的),成功率约为50%.


要从文字提示演示文稿中拍摄两张重要幻灯片,请执行以下操作:

为什么输入提示?

  1. Helps type checkers:通过提示您希望对象是哪种类型,类型判断器可以很容易地检测到,例如,您传递的对象是否具有不期望的类型.
  2. Helps with documentation:第三个人查看你的代码时,会知道你希望在哪里使用它,而不会让他们失望.
  3. Helps IDEs develop more accurate and robust tools:当你知道你的对象是什么类型时,开发环境将更适合建议合适的方法.在某个时候,您可能已经在某些IDE中体验到了这一点,达到了.,并且弹出了没有为对象定义的方法/属性.

为什么要使用静态类型判断器?

  • Find bugs sooner:我相信这是不言而喻的.
  • The larger your project the more you need it:再说一次,这是有道理的.静态语言提供了健壮性和
  • Large teams are already running static analysis:我猜这验证了前两点.

As a closing note for this small introduction:这是一个optional特性,据我所知,它的引入是为了获得静态输入的一些好处.

通常情况下,do not需要担心它,definitely不需要使用它(尤其是在使用Python作为辅助脚本语言的情况下).当开发大型项目时,它应该是有用的.


Type hinting with mypy:

为了使这个答案更完整,我认为稍微演示一下比较合适.我将使用mypy,当类型提示出现在PEP中时,它激发了类型提示的灵感.这篇文章主要是为遇到这个问题并想知道从哪里开始的任何人写的.

在此之前,让我重申以下几点:PEP 484不强制任何内容;它只是设置函数的方向 可以/应该执行how类型判断的注释和建议指南.您可以注释您的函数和 可以提示任意多的内容;不管是否存在注释,您的脚本仍将运行,因为Python本身并不使用它们.

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

  • 函数注释(PEP 3107).
  • 内置/用户模块的存根文件.
  • 补充前两种形式的 # type: type条特别注释.(参见:100获取Python 3.6更新,获得# type: type条 comments )

此外,您还需要将类型提示与Py3.5中引入的新typing模块结合使用.在它中,许多(附加的)ABCs(抽象基类)与用于静态判断的辅助函数和decorator 一起定义.collections.abc中的大多数ABC都包括在内,但采用generic形式,以便允许订阅(通过定义__getitem__()方法).

对于任何有兴趣更深入地解释这些的人来说,mypy documentation写得非常好,有很多代码示例演示/描述他们的判断器的功能;这绝对值得一读.

函数注释和特殊注释:

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

Note:如果我们想要使用containers的任何派生,并且需要指定该容器的内容,我们must使用来自typing模块的103种类型.These support indexing.

# 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,从静态Angular 讲,int是声音.这可以通过遵循a的类型并仅附加str个对象来解决,或者通过更改a内容的类型来表明任何值都是可接受的(从typing导入Any后直观地使用List[Any]执行).

函数注释以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的概念,因此不知道annotated的比较中隐藏的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"

这些可以扩展到基本上任何用例,捕获的错误可以扩展到基本调用和操作之外.你喜欢的类型

存根文件:

存根文件可用于两种不同的非互斥情况:

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

存根文件(扩展名为.pyi)是您正在制作/想要使用的模块的带注释的接口.它们包含

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,如果我们希望的话,可以在其中设置一些限制.不利的一面是 没有存根的查看源代码的人在try 理解应该是什么时,不会真正得到注释帮助 从哪里经过.

总之,存根文件的 struct 非常简单:添加所有具有空实体(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中类型提示的基本概念.即使使用了类型判断器

当我找到它们时(或者如果建议的话),我会try 在下面的列表中添加额外的checker/相关包.

100:

  • Mypy:如这里所述.
  • PyType:谷歌使用了与我收集的不同的符号,可能值得一看.

100:

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

typeshed项目实际上是您可以查看如何在您自己的项目中使用类型提示的最佳位置之一.让我们以对应的.pyi文件中的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.对于Counter类,我们可以看到它要么在初始值设定项中不带参数,要么从任何类型得到一个Mapping,要么从任何类型得到一个int,要么从任何类型得到一个Iterable.


Notice:有一件事我忘了提到,typing模块是在provisional basis上引入的.从100开始:

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

所以,在这里用一小撮盐来装东西;我怀疑它是否会被删除或以重大方式改变,但人们永远无法知道.


**另一个主题,但在类型提示的范围内有效:PEP 526: Syntax for Variable Annotations是一种通过引入新语法来取代# type条注释的努力,该语法允许用户在简单的varname: type语句中注释变量的类型.

如前所述,请参阅100,以了解这些功能的小介绍.

Python相关问答推荐

替换为Pandas

在for循环中保存和删除收件箱

Python(Polars):使用之前的变量确定当前解决方案的Vector化操作

Pandas数据帧处理Pandas表中Json内的嵌套列表以获取后续Numpy数组

为什么使用SciPy中的Distance. cos函数比直接执行其Python代码更快?

在Python中管理多个OpenGVBO和VAO实例

基本链合同的地址是如何计算的?

使用多个性能指标执行循环特征消除

从管道将Python应用程序部署到Azure Web应用程序,不包括需求包

使用polars .滤镜进行切片速度比pandas .loc慢

如何根据日期和时间将状态更新为已过期或活动?

查找两极rame中组之间的所有差异

如何使用LangChain和AzureOpenAI在Python中解决AttribeHelp和BadPressMessage错误?

Pandas - groupby字符串字段并按时间范围 Select

当从Docker的--env-file参数读取Python中的环境变量时,每个\n都会添加一个\'.如何没有额外的?

我们可以为Flask模型中的id字段主键设置默认uuid吗

递归访问嵌套字典中的元素值

在matplotlib中删除子图之间的间隙_mosaic

在Python中调用变量(特别是Tkinter)

为什么'if x is None:pass'比'x is None'单独使用更快?