我希望它不是一个副本(同时,考虑到有这么多错误的问题,很难说清楚,但这些都是基本错误),但我不明白这里会发生什么。
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中的编译器静态确定。分配给的每个名称在编译时都标记为局部变量。
- 把global ord放在函数的前面可以避免错误。
- @彼得伍德,我不是在问如何解决,我是在问一个解释。
- @Jean-Fran&231;Oisfabre我在添加缺失的信息,这是解释的一部分。这是一匹送礼马。
- 不管怎样,还是谢谢你。我知道你想帮忙。
- 顺便问一下,如果你的困惑来自于对Lisp风格范围的了解,对C风格范围的了解,或者仅仅是对直觉的了解?我试图用对这三个问题都有意义的方式来解释问题,但对这三个问题中的一个进行解释要短一些。
- 呃,它来自于"thinking I know python scoping":)谢谢您的回答。我或多或少猜对了,知道如何解决这个问题。我认为范围界定更具活力,而事实并非如此。
- 我还认为1)我需要更理性的解释,2)其他人也希望知道这一点。
- 当然。我很好奇参考文献中的解释是否足以理解这个概念。在我看来是这样的,但我知道我的判断已经被自己的判断所影响了(想想这对吉多来说是多么糟糕…)。
- 类似于"如果名称绑定操作发生在代码块内的任何位置,则块内名称的所有使用都将被视为对当前块的引用"。这可能导致在绑定块之前在块中使用名称时出错。这条规则很微妙。python缺少声明,并且允许在代码块内的任何地方执行名称绑定操作。代码块的局部变量可以通过扫描块的整个文本来确定名称绑定操作。但我不太清楚。
- 它实际上不是一个"代码块"(除非您实际上是指单个code对象)。只有函数和类(和模块)有作用域。其中包括lambda函数(不是块)和理解(实际上是函数定义加上伪装调用,而不是块),但不包括for或甚至with语句。这是另一件很容易应用的事情,一旦你学习了它,但大多数人不会学习它,直到他们第一次对它大吃一惊。
它不是关于编译器在编译到字节码时基于不相关的分支进行静态分析;它要简单得多。
python具有区分全局变量、闭包变量和局部变量的规则。函数中分配给的所有变量(包括隐式分配给的参数)都是局部变量(除非它们有global或nonlocal语句)。这在绑定和命名以及参考文档的后续章节中进行了解释。
这不是为了让口译员保持简单,而是为了让规则足够简单,使它对人类读者来说通常是直观的,并且在不直观的情况下很容易被人类理解。(这对于这种情况尤其重要,因为行为不可能在任何地方都是直观的,所以python保持规则足够简单,一旦您了解了它,这种情况仍然是显而易见的。但在那之前你必须先学习规则。当然,大多数人第一次对这条规则感到惊讶……)
即使有一个足够智能的优化器完全删除与if False: ord=None相关的任何字节码,根据语言语义规则,ord仍然是一个局部变量。
所以:你的函数中有一个ord =,因此所有对ord的引用都是对局部变量的引用,而不是任何恰好同名的全局或非局部变量,因此你的代码是UnboundLocalError。
许多人在不知道实际规则的情况下通过,而是使用一个更简单的规则:变量是
- 如果可能的话是本地的,否则
- 如果可能,则将其封闭,否则
- 如果是全球性的,否则是全球性的
- 如果是内置的,则是内置的,否则
- 一个错误
虽然这在大多数情况下都有效,但在像这样的情况下可能有点误导。使用legb scoping done lisp风格的语言会发现ord不在本地名称空间中,因此返回全局名称空间,但python不会这样做。可以说,ord位于本地命名空间中,但绑定到一个特殊的"未定义"值,实际上这与在封面下发生的情况非常接近,但这不是Python规则所说的,而且,虽然对于简单的情况,它可能更直观,但很难解释。
如果你想知道它是如何被掩盖的:
在cpython中,编译器扫描函数以查找以标识符为目标的所有分配,并将它们存储在数组中。它删除全局变量和非局部变量。这个数组最后是代码对象的co_varnames,所以假设您的ord是co_varnames[1]。然后,该变量的每次使用都被编译为LOAD_FAST 1或STORE_FAST 1,而不是LOAD_NAME或STORE_GLOBAL或其他操作。当解释时,LOAD_FAST 1只是将帧的f_locals[1]加载到堆栈上。f_locals开始时是一个空指针数组,而不是指向python对象的指针,如果LOAD_FAST加载一个空指针,它将引发UnboundLocalError。
- 使规则有点不具说服力的部分是,即使是ord += 1也使ord成为一个局部变量,尽管除非函数中有另一个ord赋值,否则这是没有意义的。
- @Svenmarnach这就是为什么"简单到可以在不直观的情况下轻松解决"部分如此重要的原因。你必须学会规则,但一旦学会了,就很难搞错。
- 对于它的价值,我认为这个规则是必要的,因为局部变量是通过索引访问的,而不是通过字典查找(用于全局变量)。否则,python可以简单地执行动态查找——首先在本地范围内查找,如果找不到名称,则在封闭范围内查找。由于局部变量是按索引而不是按名称查找的,因此编译器需要静态地确定哪些变量是局部变量,因为它需要为按索引查找而不是按名称查找创建不同的字节代码。
- @实际上不需要svenmarnach python使用"fast locals"。事实上,在2.x中,locals函数的定义方式,它确实不能使用"快速局部变量",而且黑客无论如何都不能使它工作(就像fast to locals/locals to fast在任何exec周围),因此cpython 2.x并不真正实现python2.x引用。这就是为什么3.x locals在其用途上受到了明确的限制。
- @Svenmarnach说,理解cpython在封面下所做的(并且从大约1.something时还没有真正的参考文档)绝对是有帮助的,特别是对于那些从Lisp直觉中得出这一结论的人,所以我增加了一节。
- 你说的话让我有些困惑。我知道在python 2中使用exec会使它返回到"慢"的局部变量,而在python 3中不再是这样。但是在python 2和3中,locals()的功能有什么不同呢?我查阅了文档,从python 2.7开始,它就保持了一个字一个字的一致性,我无法检测到行为上的任何差异。
- @Svenmarnach由于修改了警告(我认为是2.6和3:0),所以函数的文档是不变的,但我认为参考文档中有不同的文档,一旦我到了计算机,我将搜索这些文档。同时,最容易(但完全是人为的)交互地看到差异的方法是ctypes.pythonapi转换成局部变量dict和局部变量dict的帧函数。这也有助于了解局部变量对闭包变量的作用。在一篇写在公共汽车上的评论中很难解释,但希望你现在能模糊地指向它。
- 谢谢你回答我的问题。:)我去看看。
- 我还是找不到任何区别。从2.7开始,PyFrame_LocalsToFast()似乎一点也没有改变:旧的新的。对于PyFrame_FastToLocals()(正上方),只有错误处理发生了变化。但没关系——我不想再浪费你的时间了,我已经从看了很长一段时间后的这部分Python源代码中学到了很多东西。
只是为了演示编译器的运行情况:
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 |
- 我认为有必要对LOAD_FAST和LOAD_GLOBAL的实际情况做一个简短的解释。我试图把一句话塞进我的答案里,但你的答案,应该有更清晰的空间。(而且,你可能比我写得更简洁…)