def f():
print("Before", locals()) # line 2
print(x); # line 3
x = 2 # line 4
print("After", locals()) # line 5
x = 1
f()
Run Code Online (Sandbox Code Playgroud)
我知道 Python 中范围界定的 LEGB 规则。
对于上面的代码,当我注释掉第 4 行时,一切都按预期正常执行:对于第 3 行,python 在本地范围内找不到变量x,因此在找到它的全局范围中搜索它并打印 1。
但是当我按原样执行整个代码而不加注释时,它会引发UnboundLocalError: local variable 'x' referenced before assignment.
我确实知道我可以使用非本地和全局,但我的问题是:
我尝试在类似问题建议中找到答案,但失败了。如果我的理解有错误,请指正。
在某种程度上,答案是特定于实现的,因为Python只指定了预期的行为,而不是如何实现它。
也就是说,让我们看一下f通常的实现 CPython 生成的字节码:
>>> import dis
>>> dis.dis(f)
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Before')
4 LOAD_GLOBAL 1 (locals)
6 CALL_FUNCTION 0
8 CALL_FUNCTION 2
10 POP_TOP
3 12 LOAD_GLOBAL 0 (print)
14 LOAD_FAST 0 (x)
16 CALL_FUNCTION 1
18 POP_TOP
4 20 LOAD_CONST 2 (2)
22 STORE_FAST 0 (x)
5 24 LOAD_GLOBAL 0 (print)
26 LOAD_CONST 3 ('After')
28 LOAD_GLOBAL 1 (locals)
30 CALL_FUNCTION 0
32 CALL_FUNCTION 2
34 POP_TOP
36 LOAD_CONST 0 (None)
38 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)
有几种不同的LOAD_*操作码用于检索各种值。LOAD_GLOBAL用于全局范围内的名称;LOAD_CONST用于未分配给任何名称的本地值。LOAD_FAST用于局部变量。局部变量甚至不是通过名称存在的,而是通过数组中的索引存在的。这就是为什么它们“快”;它们以数组而不是哈希表的形式提供。(LOAD_GLOBAL也使用整数参数,但这只是名称数组的索引;名称本身仍然需要在提供全局范围的任何映射中查找。)
您甚至可以查看与 关联的常量和局部值f:
>>> f.__code__.co_consts
(None, 'Before', 2, 'After')
>>> f.__code__.co_varnames
('x',)
Run Code Online (Sandbox Code Playgroud)
LOAD_CONST 1将Before放入堆栈 因为f.__code__.co_consts[1] == 'Before',并将LOAD_FAST 0的值x放入堆栈 因为f.__code__.co_varnames[0] == 'x'。
这里的关键是字节码是在执行之前生成的。 fPython 并不是简单地在第一次看到每一行时就执行它。def除其他事项外,执行该声明还涉及:
__code__函数对象属性中的字节码。代码生成的一部分是注意到 namex是一个本地名称,由于函数体中某处的赋值(即使该函数在逻辑上无法访问),因此必须使用 来访问LOAD_FAST。
在调用时locals(实际上LOAD_FAST 0是第一次使用 before ),尚未对(ie, ) 进行赋值,因此槽 0 中没有本地值可供查找。xSTORE_FAST 0
| 归档时间: |
|
| 查看次数: |
104 次 |
| 最近记录: |