简体   繁体   English

Python 如何在遇到声明之前知道有一个局部变量?

[英]How does Python know there is a local variable before encountering its declaration?

def f(): 
    print("Before", locals())   # line 2
    print(x);                   # line 3
    x = 2                       # line 4
    print("After", locals())    # line 5

x = 1
f()

I am aware of the LEGB rule for scoping in Python.我知道 Python 中的 LEGB 范围规则。

For the above code, when I comment out line 4 , everything executes normally as expected: for line 3, python does not find variable x in the local scope and therefore searches it in the global scope where it finds it and prints 1.对于上面的代码,当我注释掉第 4 行时,一切都按预期正常执行:对于第 3 行,python 没有在本地范围内找到变量x ,因此在找到它的全局范围内搜索它并打印 1。

But when I execute the whole code as it is without commenting, it raises UnboundLocalError: local variable 'x' referenced before assignment .但是,当我在没有注释的情况下执行整个代码时,它会引发UnboundLocalError: local variable 'x' referenced before assignment

I do know I can use nonlocal and global, but my question is :我知道我可以使用非本地和全局,但我的问题是:

  1. How does python know there is a local variable declaration before it has encountered one? python如何在遇到局部变量声明之前知道有一个局部变量声明?
  2. Even if it does know there is a variable named x in the local scope (although not yet initialised), why doesn't it shows it in locals()?即使它确实知道在本地范围内有一个名为 x 的变量(尽管尚未初始化),为什么它不在 locals() 中显示它?

I tried finding the answer in similar questions suggestions but failed.我尝试在类似的问题建议中找到答案,但失败了。 Please correct if any of my understanding is wrong.如果我的任何理解有误,请更正。

To some extent, the answer is implementation specific, as Python only specifies the expected behavior, not how to implement it.在某种程度上,答案是特定于实现的,因为 Python 只指定了预期的行为,而不是如何实现它。

That said, let's look at the byte code generated for f by the usual implementation, CPython:也就是说,让我们看看通常的实现 CPython 为f生成的字节码:

>>> 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

There are several different LOAD_* op codes used to retrieve various values.有几种不同的LOAD_*操作码用于检索各种值。 LOAD_GLOBAL is used for names in the global scope; LOAD_GLOBAL用于全局范围内的名称; LOAD_CONST is used for local values not assigned to any name. LOAD_CONST用于未分配给任何名称的本地值。 LOAD_FAST is used for local variables. LOAD_FAST用于局部变量。 Local variables don't even exist by name, but by indices in an array.局部变量甚至不按名称存在,而是按数组中的索引存在。 That's why they are "fast";这就是它们“快”的原因; they are available in an array rather than a hash table.它们以数组而不是哈希表的形式提供。 ( LOAD_GLOBAL also uses integer arguments, but that's just an index into an array of names; the name itself still needs to be looked up in whatever mapping provides the global scope.) LOAD_GLOBAL也使用整数参数,但这只是名称数组的索引;名称本身仍然需要在提供全局范围的任何映射中查找。)

You can even see the constants and local values associated with f :您甚至可以看到与f关联的常量和局部值:

>>> f.__code__.co_consts
(None, 'Before', 2, 'After')
>>> f.__code__.co_varnames
('x',)

LOAD_CONST 1 puts Before on the stack because f.__code__.co_consts[1] == 'Before' , and LOAD_FAST 0 puts the value of x on the stack because f.__code__.co_varnames[0] == 'x' . LOAD_CONST 1Before放入堆栈,因为f.__code__.co_consts[1] == 'Before' ,而LOAD_FAST 0x的值放入堆栈,因为f.__code__.co_varnames[0] == 'x'

The key here is that the byte code is generated before f is ever executed.这里的关键是字节码是在f执行之前生成的。 Python isn't simply executing each line the first time it sees it. Python 不只是在第一次看到它时执行每一行。 Executing the def statement involves, among other things:执行def语句包括:

  1. reading the source code阅读源代码
  2. parsing into an abstract syntax tree (AST)解析为抽象语法树 (AST)
  3. using the entire AST to generate the byte code stored in the __code__ attribute of the function object.使用整个AST 生成存储在函数对象的__code__属性中的字节码。

Part of the code generation is noting that the name x , due to the assignment somewhere in the body of the function (even if that function is logically unreachable), is a local name, and therefore must be accessed with LOAD_FAST .代码生成的一部分注意到名称x ,由于在函数体中某处的赋值(即使该函数在逻辑上不可访问),是一个本地名称,因此必须使用LOAD_FAST访问。

At the time locals is called (and indeed before LOAD_FAST 0 is used the first time), no assignment to x (ie, STORE_FAST 0 ) has yet been made, so there is no local value in slot 0 to look up.在调用locals时(实际上是在第一次使用LOAD_FAST 0之前),尚未对x进行分配(即STORE_FAST 0 ),因此插槽 0没有要查找的本地值。

Because you define it before the f() function calling,因为你在f()函数调用之前定义了它,

let's try this one :让我们试试这个:

def f(y):
    print("Before", locals())   # line 2
    print(y);                   # line 3
    y = 2                       # line 4
    print("After", locals())    # line 5

f(x)
x = 1

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM