1

This code doesn't work:

def lol():
    i = 1
    def _lol():
        i += 1
    _lol()
lol()

Error:

local variable 'i' referenced before assignment

But, the following code works fine:

def lol():
    i = [1]
    def _lol():
        i[0] += 1
    _lol()
lol()

Why is that?

1 Answer 1

4

Python scopes fit into 3 categories -- local, nonlocal and global. By default, a function can only change a reference in the local scope (references are created with the assignment operator).

You're free to mutate an object that you have a reference to which is why the second example works (i is a reference to the list [1], then you change/mutate it's first item). In short, you're mutating the object that i references, you're not trying to change the reference. Note that you can give a function access to change the reference in the global scope via the global keyword:

i = 1
def func():
  global i  # If you comment this out, i doesn't get changed in the global scope
  i = 2

func()
print(i)  # 2 -- 1 if the global statement is commented out.

Note that python3.x adds the nonlocal keyword. It does the same thing as global but to the non-local scope. e.g.

def foo():
    i = 1  # nonlocal to bar
    def bar():
        nonlocal i 
        print(i)
        i += 1
    return bar

bar1 = foo()
bar1()  # 1
bar1()  # 2
bar1()  # 3

bar2 = foo()
bar2()  # 1
bar2()  # 2

bar1()  # 4  bar2 doesn't influence bar1 at all.

augmented operators

This is a bit more advanced, but provided to hopefully help answer questions regarding operators like +=. Consider the case:

x = []
def func():
    x += [1]

You might expect this to work -- After all, x += [1] for a list x is really just x.extend([1]), right?. Unfortunately, it's not quite. We can disassemble func using dis.dis to see a little more what's going on.

>>> dis.dis(func)
  2           0 LOAD_FAST                0 (x)
              3 LOAD_CONST               1 (1)
              6 BUILD_LIST               1
              9 INPLACE_ADD         
             10 STORE_FAST               0 (x)       ### IMPORTANT!
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE        

Notice the byte-code instruction STORE_FAST? That basically says, store the result of INPLACE_ADD in the name x in the local dictionary. In other words, you write:

x += [1]

but python executes1:

x = x.__iadd__([1])

Why? __iadd__ should operate in place so why does it need to rebind the name to __iadd__'s return value? The rebinding part is the problem -- i.e., this code would work:

x = []
def func():
    x.__iadd__([1])

The answer is because python has immutable objects and __iadd__ needs to work with them too. Because of this, __iadd__ can return an object other than "self". This ends up being incredibly useful. Consider i = 1; i += 1. This invocation only works because int.__iadd__ is allowed to return a new integer.

1Discussing this in even more depth is actually my all-time most upvoted answer on StackOverflow and can be found here

Sign up to request clarification or add additional context in comments.

4 Comments

Could you also cover this: i=[]; def x(): i+=[2] - why doesn't that work? += on lists doesn't reassign anything...
@georg -- Sure it does. The assignment is just a little hidden. If you disassemble that function using dis, you'll see a STORE_FAST ... (i) instruction. i += something calls i.__iadd__(something) and stores the return value in the name i in the local scope. if it didn't, += wouldn't work for immutable objects (i.e. int). Note that cls.__iadd__ should (but doesn't have to) return self for mutable objects and a new instance for immutable objects.
Thanks )) I mean, don't you think this would be worth adding to your answer?
@georg -- I didn't want to complicate things too much, but . . . Hopefully I've made it clear that you don't need to look at that section unless you're actually interested . . .

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.