类型提示:为什么 python 自引用需要引号?

Typehinting: Why does python self-referencing require quotes?

根据PEP 484,

When a type hint contains names that have not been defined yet, that definition may be expressed as a string literal, to be resolved later.

我们看到了以下(非法)代码:

class Tree:
    def __init__(self, left: Tree, right: Tree):
        self.left = left
        self.right = right

这似乎是类型提示的一种直观实现,人们会期望由于定义了符号 Tree,所以 python 会知道它。

我的工作理论是,虽然 python 是逐行 运行,但符号 Tree 尚未添加到命名空间字典中,因为 Tree class 仍在定义中。因此,尽管 symbol 存在,但 object 尚不存在。这是正确的吗?有没有比我给出的更细微的差别?

因此,Python 是 运行 “line-by-line”,但那是 字节码 。让我们考虑以下示例:

In [3]: class Foo:
   ...:     def bar(self) -> Foo:
   ...:         return Foo()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Input In [3], in <module>
----> 1 class Foo:
      2     def bar(self) -> Foo:
      3         return Foo()

Input In [3], in Foo()
      1 class Foo:
----> 2     def bar(self) -> Foo:
      3         return Foo()

NameError: name 'Foo' is not defined

然后让我们反汇编一下字节码:

In [5]: dis.dis(
   ...:     """
   ...: class Foo:
   ...:     def bar(self) -> Foo:
   ...:         return Foo()
   ...: """
   ...: )
  2           0 LOAD_BUILD_CLASS
              2 LOAD_CONST               0 (<code object Foo at 0x10b26bc90, file "<dis>", line 2>)
              4 LOAD_CONST               1 ('Foo')
              6 MAKE_FUNCTION            0
              8 LOAD_CONST               1 ('Foo')
             10 CALL_FUNCTION            2
             12 STORE_NAME               0 (Foo)
             14 LOAD_CONST               2 (None)
             16 RETURN_VALUE

Disassembly of <code object Foo at 0x10b26bc90, file "<dis>", line 2>:
  2           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('Foo')
              6 STORE_NAME               2 (__qualname__)

  3           8 LOAD_NAME                3 (Foo)
             10 LOAD_CONST               1 (('return',))
             12 BUILD_CONST_KEY_MAP      1
             14 LOAD_CONST               2 (<code object bar at 0x10b264d40, file "<dis>", line 3>)
             16 LOAD_CONST               3 ('Foo.bar')
             18 MAKE_FUNCTION            4 (annotations)
             20 STORE_NAME               4 (bar)
             22 LOAD_CONST               4 (None)
             24 RETURN_VALUE

Disassembly of <code object bar at 0x10b264d40, file "<dis>", line 3>:
  4           0 LOAD_GLOBAL              0 (Foo)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE

查看顶层,以 LOAD_BUILD_CLASS 开头的那一层。这实质上将 __build_class__ 放在堆栈的顶部,这是用于实际创建 class 对象的辅助函数。如果你想了解函数中发生的细节,start reading from here。但出于我们的目的,它基本上选择了正确的 metaclass(所以 type 除非你另有说明,或者继承自 class ),并准备 class 命名空间,并且 class_object = metaclass(name, bases, namespace, **kwds)。出于好奇,class 命名空间本质上是通过初始化它创建的,namespace = {} 然后是 exec(body, globals(), namespace)(虽然不完全是,再次阅读更多关于 nitty-gritty 的数据模型文档详情)。

但基本上,如您所见,直到 class 创建函数被调用后,变量才被创建:

    12 STORE_NAME               0 (Foo) 

现在,:

    3           8 LOAD_NAME                3 (Foo)

用于创建 return 注释的将在全局命名空间中搜索 Foo,但还没有找到,它会引发错误!

注意,您可以使用 from __future__ import annotations,这将 工作 ,本质上,注释的评估被推迟,注释本质上作为字符串存储在注释中. 请注意,当我在使用 from __future__ import annotations 后尝试时,dis 实际上似乎并没有给出不同的输出,但这可能只是 dis 模块的限制.

如果我们在传递给 dis 的代码中使用 __future__ 导入,我们会看到:

In [6]: dis.dis(
   ...:     """
   ...: from __future__ import annotations
   ...: class Foo:
   ...:     def bar(self) -> Foo:
   ...:         return Foo()
   ...: """
   ...: )
  2           0 LOAD_CONST               0 (0)
              2 LOAD_CONST               1 (('annotations',))
              4 IMPORT_NAME              0 (__future__)
              6 IMPORT_FROM              1 (annotations)
              8 STORE_NAME               1 (annotations)
             10 POP_TOP

  3          12 LOAD_BUILD_CLASS
             14 LOAD_CONST               2 (<code object Foo at 0x10819c2f0, file "<dis>", line 3>)
             16 LOAD_CONST               3 ('Foo')
             18 MAKE_FUNCTION            0
             20 LOAD_CONST               3 ('Foo')
             22 CALL_FUNCTION            2
             24 STORE_NAME               2 (Foo)
             26 LOAD_CONST               4 (None)
             28 RETURN_VALUE

Disassembly of <code object Foo at 0x10819c2f0, file "<dis>", line 3>:
  3           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('Foo')
              6 STORE_NAME               2 (__qualname__)

  4           8 LOAD_CONST               0 ('Foo')
             10 LOAD_CONST               1 (('return',))
             12 BUILD_CONST_KEY_MAP      1
             14 LOAD_CONST               2 (<code object bar at 0x10819c240, file "<dis>", line 4>)
             16 LOAD_CONST               3 ('Foo.bar')
             18 MAKE_FUNCTION            4 (annotations)
             20 STORE_NAME               3 (bar)
             22 LOAD_CONST               4 (None)
             24 RETURN_VALUE

Disassembly of <code object bar at 0x10819c240, file "<dis>", line 4>:
  5           0 LOAD_GLOBAL              0 (Foo)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE

请注意,“线”变为:

      4           8 LOAD_CONST               0 ('Foo')

无论如何,阅读更多相关内容 here