Python variable scope error

2018-12-30 23:50发布

The following code works as expected in both Python 2.5 and 3.0:

a, b, c = (1, 2, 3)

print(a, b, c)

def test():
    print(a)
    print(b)
    print(c)    # (A)
    #c+=1       # (B)
test()

However, when I uncomment line (B), I get an UnboundLocalError: 'c' not assigned at line (A). The values of a and b are printed correctly. This has me completely baffled for two reasons:

  1. Why is there a runtime error thrown at line (A) because of a later statement on line (B)?

  2. Why are variables a and b printed as expected, while c raises an error?

The only explanation I can come up with is that a local variable c is created by the assignment c+=1, which takes precedent over the "global" variable c even before the local variable is created. Of course, it doesn't make sense for a variable to "steal" scope before it exists.

Could someone please explain this behavior?

10条回答
柔情千种
2楼-- · 2018-12-31 00:24

Taking a look at the disassembly may clarify what is happening:

>>> def f():
...    print a
...    print b
...    a = 1

>>> import dis
>>> dis.dis(f)

  2           0 LOAD_FAST                0 (a)
              3 PRINT_ITEM
              4 PRINT_NEWLINE

  3           5 LOAD_GLOBAL              0 (b)
              8 PRINT_ITEM
              9 PRINT_NEWLINE

  4          10 LOAD_CONST               1 (1)
             13 STORE_FAST               0 (a)
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE

As you can see, the bytecode for accessing a is LOAD_FAST, and for b, LOAD_GLOBAL. This is because the compiler has identified that a is assigned to within the function, and classified it as a local variable. The access mechanism for locals is fundamentally different for globals - they are statically assigned an offset in the frame's variables table, meaning lookup is a quick index, rather than the more expensive dict lookup as for globals. Because of this, Python is reading the print a line as "get the value of local variable 'a' held in slot 0, and print it", and when it detects that this variable is still uninitialised, raises an exception.

查看更多
情到深处是孤独
3楼-- · 2018-12-31 00:28

c+=1 assigns c, python assumes assigned variables are local, but in this case it hasn't been declared locally.

Either use the global or nonlocal keywords.

nonlocal works only in python 3, so if you're using python 2 and don't want to make your variable global, you can use a mutable object:

my_variables = { # a mutable object
    'c': 3
}

def test():
    my_variables['c'] +=1

test()
查看更多
人间绝色
4楼-- · 2018-12-31 00:30

Python treats variables in functions differently depending on whether you assign values to them from within the function or not. If a function contains any assignments to a variable, it is treated by default as a local variable. Therefore, when you uncomment the line, you are trying to reference a local variable before any value has been assigned to it.

If you want the variable c to refer to the global c put

global c

as the first line of the function.

As for python 3, there is now

nonlocal c

that you can use to refer to the nearest enclosing function scope that has a c variable.

查看更多
心情的温度
5楼-- · 2018-12-31 00:35

Python has rather interesting behavior when you try traditional global variable semantics. I don't remember the details, but you can read the value of a variable declared in 'global' scope just fine, but if you want to modify it, you have to use the global keyword. Try changing test() to this:

def test():
    global c
    print(a)
    print(b)
    print(c)    # (A)
    c+=1        # (B)

Also, the reason you are getting this error is because you can also declare a new variable inside that function with the same name as a 'global' one, and it would be completely separate. The interpreter thinks you are trying to make a new variable in this scope called c and modify it all in one operation, which isn't allowed in Python because this new c wasn't initialized.

查看更多
倾城一夜雪
6楼-- · 2018-12-31 00:39

The best example that makes it clear is:

bar = 42
def foo():
    print bar
    if False:
        bar = 0

when calling foo() , this also raises UnboundLocalError although we will never reach to line bar=0, so logically local variable should never be created.

The mystery lies in "Python is an Interpreted Language" and the declaration of the function foo is interpreted as a single statement (i.e. a compound statement), it just interprets it dumbly and creates local and global scopes. So bar is recognized in local scope before execution.

For more examples like this Read this post: http://blog.amir.rachum.com/blog/2013/07/09/python-common-newbie-mistakes-part-2/

This post provides a Complete Description and Analyses of the Python Scoping of variables:

查看更多
皆成旧梦
7楼-- · 2018-12-31 00:39

This is not a direct answer to your question, but it is closely related, as it's another gotcha caused by the relationship between augmented assignment and function scopes.

In most cases, you tend to think of augmented assignment (a += b) as exactly equivalent to simple assignment (a = a + b). It is possible to get into some trouble with this though, in one corner case. Let me explain:

The way Python's simple assignment works means that if a is passed into a function (like func(a); note that Python is always pass-by-reference), then a = a + b will not modify the a that is passed in. Instead, it will just modify the local pointer to a.

But if you use a += b, then it is sometimes implemented as:

a = a + b

or sometimes (if the method exists) as:

a.__iadd__(b)

In the first case (as long as a is not declared global), there are no side-effects outside local scope, as the assignment to a is just a pointer update.

In the second case, a will actually modify itself, so all references to a will point to the modified version. This is demonstrated by the following code:

def copy_on_write(a):
      a = a + a
def inplace_add(a):
      a += a
a = [1]
copy_on_write(a)
print a # [1]
inplace_add(a)
print a # [1, 1]
b = 1
copy_on_write(b)
print b # [1]
inplace_add(b)
print b # 1

So the trick is to avoid augmented assignment on function arguments (I try to only use it for local/loop variables). Use simple assignment, and you will be safe from ambiguous behaviour.

查看更多
登录 后发表回答