Local and global references with UnboundLocalError

2019-07-13 19:12发布

问题:

I don't quite understand why the code

def f():
    print(s)

s = "foo"
f()

runs perfectly fine but

def f():
    print(s)
    s = "bar"

s = "foo"
f()

gives me UnboundLocalError. I know that I can fix this by declaring s as a global variable inside the function or by simply passing s an an argument into the function.

Still I don't understand how python seemingly knows whether or not s is referenced inside the function before the line has been executed? Does python make some sort of list of all local variable references when the function is read into the global frame?

回答1:

Other answers have focused on the practical aspects of this but have not actually answered the question you asked.

Yes, the Python compiler tracks which variables are assigned when it is compiling a code block (such as in a def). If a name is assigned to in a block, the compiler marks it as local.Take a look at function.__code__.co_varnames to see which variables the compiler has identified.

The nonlocal and global statements can override this.



回答2:

Yes, Python will look-ahead to recover all variables declared in the local scope. These will then overshadow global variables.

So in your code:

def f():
    print(s)

s = "foo"
f()

Python did not find s in the local scope, so it tries to recover it from the global scope and finds "foo".

Now in the other case the following happens:

def f():
    print(s)
    s = "bar

s = "foo"
f()

Python knows that s is a local variable because it did a look-ahead before runtime, but at runtime it was not assigned yet so it raised and exception.

Note that Python will even let you reference variables that have not been declared anywhere. If you do:

def foo():
    return x
f()

You will get a NameError, because Python, when not finding, x as a local variable will just remember that at runtime it should look for a global variable named x and then fail if it does not exist.

So UnboundLocalError means that the variable may eventually be declared in scope but has not been yet. On the other hand NameError means that the variable will never be declared in the local scope, so Python tried to find it in the global scope, but it did not exist.