0

Came across python environment concept in a programming course and had a following question.

For example:

(1)

>>>def f(x):
       def g(y):
           return x - y
        return g
>>> f(2)(3)
-1

(2)

def f(x):
    def g(y):
        x = x - y
        return x
    return g
>>> f(2)(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in g
UnboundLocalError: local variable 'x' referenced before assignment

(3)

>>> def f(x):
        def g(y):
            if x > y:
               x = x - y
            else:
               x = y - x
            return x
        return g
>>> f(2)(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in g
UnboundLocalError: local variable 'x' referenced before assignment

example(1) can refer 'x' from its parent frame but the (2) and (3) can't. I couldn't figure out why. And if (2) fails because of assignment to a non-local name, why (3) gives an error at line 3, not line 4?

cheddar
  • 17
  • 9

1 Answers1

0

Whenever you 'refer' to a name in Python, you are implicitly performing an unqualified name lookup. These follow the LEGB rule: the system looks for bindings of the name in different places in the order

  1. In the Local scope of the function in which the lookup is being done (if any)
  2. In the local scope of functions Enclosing the one in which the lookup is being done (if any)
  3. In the Global scope
  4. In the Builtins module.

The search stops as soon as it finds a binding of the name you seek. If the name is bound in more than one of those places, the earlier ones in the list are said to shadow the later ones.

In your first code sample, x is bound in E/f and y is bound in L/g. Both are parameters of functions, and function parameters are bound in the local scope of the function.

In your second sample x = binds the name x in the Local scope of the inner function g, thereby shadowing the binding of x in the Eenclosing function f. Because of this, LEGB no longer finds x in E/f but in L/g.

By seeing the x = written inside g Python decides at compile time that x's binding comes from g. Unfortunately, at runtime the binding of x in g has not been established yet. Which is why you get an UnboundLocalError.

In your third example, exactly the same problem occurs: On line 4 the compiler discovers that there is a binding of x in L/g, which shadows the binding of x in E/f, and therefore the binding of x in g will be used at runtime. At runtime we get to line 3 (where the name is looked up) before line 4 (where the name is bound), so once again UnboundLocalError.

The error is at line 3, because that is the line on which the lookup fails.

In summary, there is nothing wrong with assigning to x in g. The problem occurs when you try to lookup the value of x in g before it has been established.

If you are in Python 3, you could add the declaration nonlocal x inside the inner function g. This means: Dear compiler, if anywhere in this scope you find code that attempts to bind x, do not create a binding in this scope, instead, rebind the name in whatever enclosing scope has already provided the binding.

jacg
  • 2,040
  • 1
  • 14
  • 27
  • thank you so much @jacg, this is an excellent explain. Another question about python compiler and runtime, which I'm not familiar with. Does the *compiler* work from left to right (bind `x` first)? And during *runtime*, is the right hand of `=` evaluated first(`x = x - y` in ex2)? Then, it can't find `x` during *runtime* because `x` is bound to a local variable during *compile time* which hasn't been initialized yet. – cheddar Oct 09 '17 at 03:13
  • @Z.Chi At runtime the RHS of an assignment must be evaluated before the LHS is bound: how could the value be bound before it has been calculated! It's not really meaningful to say that the compiler works left-to-right in this case. What matters is that the compiler analyses the structure of the code before runtime. It's not quite what you say in your last sentence: the compiler does not bind `x`, but decides *where* `x` will be bound. Then the lookup happend before the binding at runtime. – jacg Oct 09 '17 at 05:45
  • @Z.Chi BTW, if you think that the answer is a good one, you should upvote it. If you think it solves your problem, then you should accept it. – jacg Oct 09 '17 at 05:46