Why can functions in Python print variables in enc

2019-01-11 03:26发布

问题:

If I run the following code:

x = 1

class Incr:
    print(x)
    x = x + 1
    print(x)

print(x)

It prints:

1
2
1

Okay no problems, that's exactly what I expected. And if I do the following:

x = 1

class Incr:
    global x
    print(x)
    x = x + 1
    print(x)

print(x)

It prints:

1
2
2

Also what I expected. No problems there.

Now if I start making an increment function as follows:

x = 1

def incr():
    print(x)

incr()

It prints 1 just as I expected. I assume it does this because it cannot find x in its local scope, so it searches its enclosing scope and finds x there. So far no problems.

Now if I do:

x = 1

def incr():
    print(x)
    x = x + 1

incr()

This gives me the following error in the traceback:

UnboundLocalError: local variable 'x' referenced before assignment.

Why does Python not just search the enclosing space for x when it cannot find a value of x to use for the assignment like my class Incr did? Note that I am not asking how to make this function work. I know the function will work if I do the following:

x = 1

def incr():
    global x
    print(x)
    x = x + 1
    print(x)

incr()

This will correctly print:

1
2

just as I expect. All I am asking is why it doesn't just pull x from the enclosing scope when the keyword global is not present just like it did for my class above. Why does the interpreter feel the need to report this as an UnboundLocalError when clearly it knows that some x exists. Since the function was able to read the value at x for printing, I know that it has x as part of its enclosing scope...so why does this not work just like the class example?

Why is using the value of x for print so different from using its value for assignment? I just don't get it.

回答1:

Classes and functions are different, variables inside a class are actually assigned to the class's namespace as its attributes, while inside a function the variables are just normal variables that cannot be accessed outside of it.

The local variables inside a function are actually decided when the function gets parsed for the first time, and python will not search for them in global scope because it knows that you declared it as a local variable.

So, as soon as python sees a x = x + 1(assignment) and there's no global declared for that variable then python will not look for that variable in global or other scopes.

>>> x = 'outer'
>>> def func():
...     x = 'inner'  #x is a local variable now
...     print x
...     
>>> func()
inner

Common gotcha:

>>> x = 'outer'
>>> def func():
...     print x       #this won't access the global `x`
...     x = 'inner'   #`x` is a local variable
...     print x
...     
>>> func()
...
UnboundLocalError: local variable 'x' referenced before assignment

But when you use a global statement then python for look for that variable in global scope.

Read: Why am I getting an UnboundLocalError when the variable has a value?

nonlocal: For nested functions you can use the nonlocal statement in py3.x to modify a variable declared in an enclosing function.


But classes work differently, a variable x declared inside a class A actually becomes A.x:

>>> x = 'outer'
>>> class A:
...    x += 'inside'  #use the value of global `x` to create a new attribute `A.x`
...    print x        #prints `A.x`
...     
outerinside
>>> print x
outer

You can also access the class attributes directly from global scope as well:

>>> A.x
'outerinside'

Using global in class:

>>> x = 'outer'
>>> class A:
...     global x
...     x += 'inner' #now x is not a class attribute, you just modified the global x
...     print x
...     
outerinner
>>> x
'outerinner'
>>> A.x
AttributeError: class A has no attribute 'x'

Function's gotcha will not raise an error in classes:

>>> x = 'outer'
>>> class A:
...     print x                      #fetch from globals or builitns
...     x = 'I am a class attribute' #declare a class attribute
...     print x                      #print class attribute, i.e `A.x`
...     
outer
I am a class attribute
>>> x
'outer'
>>> A.x
'I am a class attribute'

LEGB rule: if no global and nonlocal is used then python searches in this order.

>>> outer = 'global'
>>> def func():
        enclosing = 'enclosing'
        def inner():
                inner = 'inner'
                print inner           #fetch from (L)ocal scope
                print enclosing       #fetch from (E)nclosing scope
                print outer           #fetch from (G)lobal scope
                print any             #fetch from (B)uilt-ins
        inner()
...         
>>> func()
inner
enclosing
global
<built-in function any>


回答2:

From Python scopes and namespaces:

It is important to realize that scopes are determined textually: the global scope of a function defined in a module is that module’s namespace, no matter from where or by what alias the function is called. On the other hand, the actual search for names is done dynamically, at run time — however, the language definition is evolving towards static name resolution, at “compile” time, so don’t rely on dynamic name resolution! (In fact, local variables are already determined statically.)

Which means that, the scope for x = x + 1 is determined statically, before the function is called. And since this is an assignment, then 'x' becomes a local variable, and not looked up globally.

That is also the reason why from mod import * is disallowed in functions. Because the interpreter won't import modules for you in compile time to know the names you are using in the function. i.e, it must know all names referenced in the function at compile time.



回答3:

It's the rule Python follows - get used to it ;-) There is a practical reason: both the compiler and human readers can determine which variables are local by looking only at the function. Which names are local has nothing to do with the context in which a function appears, and it's generally a Very Good Idea to follow rules that limit the amount of source code you have to stare at to answer a question.

About:

I assume it does this because it cannot find x in its local scope, so it searches its enclosing scope and finds x.

Not quite: the compiler determines at compile time which names are and aren't local. There's no dynamic "hmm - is this local or global?" search going on at runtime. The precise rules are spelled out here.

As to why you don't need to declare a name global just to reference its value, I like Fredrik Lundh's old answer here. In practice, it is indeed valuable that a global statement alerts code readers to that a function may be rebinding a global name.



回答4:

Because that's the way it was designed to work.

Basically, if you have an assignment anywhere in your function, then that variable becomes local to that function (unless you've used global, of course).



回答5:

Because it would lead to very hard to track down bugs!

When you type that x = x + 1, you could have meant to increment an x in the enclosing scope... or you could have simply forgotten that you already used x somewhere else and been trying to declare a local variable.

I would prefer the interpreter to only allow you to change the parent namespace if you intend to- this way you can't do it by accident.



回答6:

I can try and make an educated guess why it works this way.

When Python encounters a string x = x + 1 in your function, it has to decide where to look up the x.

It could say "the first occurrence of x is global, and the second one is local", but this is quite ambiguous (and therefore against Python philosophy). This could be made part of the syntax, but it potentially leads to tricky bugs. Therefore it was decided to be consistent about it, and treat all occurrences as either global or local variables.

There is an assignment, therefore if x was supposed to be global, there would be a global statement, but none is found.

Therefore, x is local, but it is not bound to anything, and yet it is used in the expression x + 1. Throw UnboundLocalError.



回答7:

As an extra example to the newly created A.x created within the class. The reassigning of x to 'inner' within the class does not update the global value of x because it is now a class variable.

x = 'outer'
class A:
    x = x
    print(x)
    x = 'inner'
    print(x)

print(x)
print(A.x)


标签: python scope