为什么' ord'在这里看作未分配的变量?

时间:2018-03-28 15:39:28

标签: python variables

我希望它不是重复的(同时很难说,考虑到这些错误的问题数量,但这些都是基本的错误),但我不会这样做。了解这里发生了什么。

def f():
    c = ord('a')

f()

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

if False:
    ord = None
def f():
    c = ord('a')

f()

同样运行,没有错误(ord未被覆盖,条件始终为false)。现在:

def f():
    if False:
        ord = None
    c = ord('a')

f()

我得到(c = ord('a')

UnboundLocalError: local variable 'ord' referenced before assignment

似乎只引用左侧操作数使其成为局部变量,即使代码未运行。

显然我可以解决这个问题,但是我感到很惊讶,因为python的动态方面允许你定义一个变量,就像是一个整数,并在下一行将它定义为一个字符串

似乎与What's the scope of a variable initialized in an if statement?

有关

显然,解释器在编译为字节码时仍会记录未到达的分支,但究竟会发生什么?

(在Python 2.7和Python 3.4上测试)

2 个答案:

答案 0 :(得分:13)

编译到字节码时,编译器是否根据不相关的分支进行静态分析;它简单得多。

Python有一个区分全局变量,闭包变量和局部变量的规则。在函数中分配给的所有变量(包括隐式分配的参数)都是局部变量(除非它们有globalnonlocal语句)。这在Binding and Naming以及参考文档中的后续部分中进行了解释。

这不是关于保持翻译简单,而是保持规则足够简单以至于人类读者通常是直观的,并且很容易被人类解决,当它不是&# 39;直观。 (这对于这样的情况尤其重要 - 行为在任何地方都可以是直观的,因此Python保持规则足够简单,一旦你学会了,这样的情况仍然很明显。但你肯定有在那之前学习规则是正确的。当然,大多数人第一次被它惊讶地学习规则......)

即使优化器足够聪明,可以完全删除与if False: ord=None相关的任何字节码,ord仍然必须是语言语义规则的局部变量。

所以:你的函数中有ord =,因此对ord的所有引用都是对局部变量的引用,而不是任何碰巧具有相同名称的全局或非本地变量,以及因此,您的代码为UnboundLocalError

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

  • 如果可能是本地,否则
  • 如果可能,可以包围,否则
  • 全局如果它在全局中,否则
  • 如果它在内置,则为内置,否则
  • 错误

虽然这适用于大多数情况,但在某些情况下可能会有点误导 - 就像这样。 LEGB范围完成Lisp风格的语言会看到ord不在本地命名空间中,因此返回全局,但Python不会这样做。您可以说本地命名空间中ord ,但绑定到特殊的" undefined"价值,这实际上接近封底下发生的事情,但这不是Python的规则所说的,虽然对于简单的案例可能更直观,但它更难以实现理由通过。

如果你好奇这是如何运作的:

在CPython中,编译器会扫描您的函数以查找标识符作为目标的所有赋值,并将它们存储在数组中。它删除了全局变量和非局部变量。此数组最终作为您的代码对象co_varnames,因此请说明您的ordco_varnames[1]。然后,对该变量的每次使用都会编译为LOAD_FAST 1STORE_FAST 1,而不是LOAD_NAMESTORE_GLOBAL或其他操作。 LOAD_FAST 1只会在解释时将框架f_locals[1]加载到堆栈中。 f_locals以NULL指针数组开始,而不是指向Python对象的指针,如果LOAD_FAST加载NULL指针,则会引发UnboundLocalError

答案 1 :(得分:2)

只是为了演示编译器的用途:

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代替:

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