Python 中没有赋值的类型提示

skc*_*pto 7 python annotations type-hinting python-typing

我的印象是typingPython 中的模块主要是为了提高代码可读性和代码文档目的。

在玩过它并阅读了有关该模块的内容之后,我设法让自己对它感到困惑。

即使这两个变量未初始化(就像您通常初始化它们一样,例如a = "test"),下面的代码也可以工作。

我只在上面添加了类型提示,一切看起来都很好。也就是说,我没有得到 a ,就像我在代码中NameError得到的那样aNameError: name 'a' is not defined

以这种方式声明变量(带有类型提示)是一种好的做法吗?为什么这有效?

from typing import Any

test_var: int
a: Any

print('hi')
Run Code Online (Sandbox Code Playgroud)

我期望test_var: int返回一个错误,指出该错误test_var尚未启动,并且我必须执行类似的操作test_var: int = 0(或任何值)。是否因为我向其中添加了类型提示而将其设置为默认值?

Dan*_*erg 11

当您考虑所涉及的名称空间时,这相当简单。NameError当您实际尝试使用 执行任何操作test_var(例如将其传递给函数(如))时,您会得到 , ,这一事实暗示了这一点print。它告诉您口译员不知道您使用的名称。

变量赋值有什么作用?

当您第一次为模块的全局命名空间中的变量分配值时,会发生什么情况,它会被添加到该模块的全局字典中,键是变量名称,值是它的值。您可以通过调用该模块中的内置globals函数来查看该字典:

from pprint import pprint

a = 1

pprint(globals())
Run Code Online (Sandbox Code Playgroud)

输出看起来像这样:

{'__annotations__': {},
 ...
 '__name__': '__main__',
 ...
 'a': 1,
 ...}
Run Code Online (Sandbox Code Playgroud)

注释有什么作用?

当您仔细查看该字典时,您会发现其中另一个有趣的键,即__annotations__。现在,它的值是一个空字典。但我打赌你已经可以猜到,如果我们用类型注释我们的变量,会发生什么:

{'__annotations__': {},
 ...
 '__name__': '__main__',
 ...
 'a': 1,
 ...}
Run Code Online (Sandbox Code Playgroud)

输出:

{'__annotations__': {'a': <class 'int'>},
 ...
 'a': 1,
 ...}
Run Code Online (Sandbox Code Playgroud)

当我们向变量添加类型提示(即注释__annotations__)时,解释器会将该名称和类型添加到相关字典中(请参阅文档);在本例中是我们的模块。顺便说一句,由于__annotations__字典位于我们的全局命名空间中,我们可以直接访问它:

from pprint import pprint

a: int = 1

pprint(globals())
Run Code Online (Sandbox Code Playgroud)

可以不赋值就注释吗?

最后,如果我们只是注释而不给变量赋值,会发​​生什么?

{'__annotations__': {'a': <class 'int'>},
 ...
 'a': 1,
 ...}
Run Code Online (Sandbox Code Playgroud)

这就是为什么我们会得到一个错误的解释,如果我们尝试a在这个例子中打印出来,但否则不会得到任何错误。该代码只是告诉解释器(以及任何静态类型检查器)有关注释的信息,但它没有分配任何值,因此无法在全局命名空间字典中创建条目。

如果您考虑一下,这是有道理的:应该将什么设置为该名称空间中的值a?它没有任何价值(甚至没有None任何NotImplemented价值)。对于解释器来说,该行仅意味着在我们的模块a: int中创建一个条目,这是完全有效的。__annotations__

注解的运行时含义

我还想强调这样一个事实,即注释对于解释器和运行时来说并不是毫无意义的,正如一些人经常声称的那样。诚然,它很少被使用,但正如我们在示例中看到的,您绝对可以在运行时使用注释。这是否有用显然取决于您。一些包(例如Pydantic或标准库)dataclasses实际上严重依赖注释来实现其目的。

__annotations__在我们的示例中,字典中设置的值实际上是对该类的引用int。因此,如果我们愿意的话,我们绝对可以在运行时使用它:

a: int = 1

print("a" in globals())        # True
print("a" in __annotations__)  # True
Run Code Online (Sandbox Code Playgroud)

您也可以在类名称空间中使用这个概念(不仅仅是模块名称空间),但我将把它作为练习留给读者。

总而言之,要将名称添加到任何命名空间,必须为其分配一个值。分配值而仅提供注释完全可以在该命名空间的__annotations__.