关于python:为什么’ord’被视为未分配的变量?

why is 'ord' seen as an unassigned variable here?

我希望它不是一个副本(同时,考虑到有这么多错误的问题,很难说清楚,但这些都是基本错误),但我不明白这里会发生什么。

1
2
3
4
def f():
    c = ord('a')

f()

运行,无错误(ord将字符转换为ASCII代码,这是内置的)。现在:

1
2
3
4
5
6
if False:
    ord = None
def f():
    c = ord('a')

f()

同样运行,没有错误(ord不会被覆盖,条件总是错误的)。现在:

1
2
3
4
5
6
def f():
    if False:
        ord = None
    c = ord('a')

f()

我得到(在c = ord('a')的线路上)

1
UnboundLocalError: local variable 'ord' referenced before assignment

似乎只要引用一个左边的操作数,它就会成为一个局部变量,即使代码没有运行。

显然,我可以解决这个问题,但是我非常惊讶,因为python的动态方面允许您定义一个像整数一样的变量,并且在下一行将其定义为字符串。

它似乎与if语句中初始化的变量的作用域有关?

显然,在编译到字节码时,解释器仍然记录未实现的分支,但是具体发生了什么?

(在python 2.7和python 3.4上测试)


它不是关于编译器在编译到字节码时基于不相关的分支进行静态分析;它要简单得多。

python具有区分全局变量、闭包变量和局部变量的规则。函数中分配给的所有变量(包括隐式分配给的参数)都是局部变量(除非它们有globalnonlocal语句)。这在绑定和命名以及参考文档的后续章节中进行了解释。

这不是为了让口译员保持简单,而是为了让规则足够简单,使它对人类读者来说通常是直观的,并且在不直观的情况下很容易被人类理解。(这对于这种情况尤其重要,因为行为不可能在任何地方都是直观的,所以python保持规则足够简单,一旦您了解了它,这种情况仍然是显而易见的。但在那之前你必须先学习规则。当然,大多数人第一次对这条规则感到惊讶……)

即使有一个足够智能的优化器完全删除与if False: ord=None相关的任何字节码,根据语言语义规则,ord仍然是一个局部变量。

所以:你的函数中有一个ord =,因此所有对ord的引用都是对局部变量的引用,而不是任何恰好同名的全局或非局部变量,因此你的代码是UnboundLocalError

许多人在不知道实际规则的情况下通过,而是使用一个更简单的规则:变量是

  • 如果可能的话是本地的,否则
  • 如果可能,则将其封闭,否则
  • 如果是全球性的,否则是全球性的
  • 如果是内置的,则是内置的,否则
  • 一个错误

虽然这在大多数情况下都有效,但在像这样的情况下可能有点误导。使用legb scoping done lisp风格的语言会发现ord不在本地名称空间中,因此返回全局名称空间,但python不会这样做。可以说,ord位于本地命名空间中,但绑定到一个特殊的"未定义"值,实际上这与在封面下发生的情况非常接近,但这不是Python规则所说的,而且,虽然对于简单的情况,它可能更直观,但很难解释。

如果你想知道它是如何被掩盖的:

在cpython中,编译器扫描函数以查找以标识符为目标的所有分配,并将它们存储在数组中。它删除全局变量和非局部变量。这个数组最后是代码对象的co_varnames,所以假设您的ordco_varnames[1]。然后,该变量的每次使用都被编译为LOAD_FAST 1STORE_FAST 1,而不是LOAD_NAMESTORE_GLOBAL或其他操作。当解释时,LOAD_FAST 1只是将帧的f_locals[1]加载到堆栈上。f_locals开始时是一个空指针数组,而不是指向python对象的指针,如果LOAD_FAST加载一个空指针,它将引发UnboundLocalError


只是为了演示编译器的运行情况:

1
2
3
4
5
6
7
8
9
10
11
def f():
    if False:
        ord = None
    c = ord('a')

  4           0 LOAD_FAST                0 (ord)
              3 LOAD_CONST               1 ('a')
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 STORE_FAST               1 (c)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE

访问a时使用的是LOAD_FAST,用于局部变量。

如果将ord设置为"无",则使用LOAD_GLOBAL

1
2
3
4
5
6
7
8
9
10
11
if False:
    ord = None
def f():
    c = ord('a')

  4           0 LOAD_GLOBAL              0 (ord)
              3 LOAD_CONST               1 ('a')
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 STORE_FAST               0 (c)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE