1

I have some code from a beginner's coding exercise:

numbers = []
i = 0

def populate(maximum, step):
    while i < maximum:
        numbers.append(i)
        i = i + step

populate(10, 2)

Which fails with the stack trace:

Traceback (most recent call last):
    File "test_python.py", line 9, in <module>
        populate(10, 2)
    File "test_python.py", line 5, in populate
        while i < maximum:
UnboundLocalError: local variable 'i' referenced before assignment

Here is my understanding of the problem so far...

  • i is an int and therefore it is immutable, and numbers is a list and is mutable
  • Since i is immutable, it can be read while it is out of scope. However, overwritting it while it is not in scope (ie. in the populate function), causes an UnboundLocalError
  • Since numbers is a mutable list, appending to it does not cause an overwrite and therefore does not cause an UnboundLocalError
  • If a simple change is made to the populate method, the program works successfully (due to i not being overwritten)

    def populate(maximum, step): new_i = i while new_i < maximum: numbers.append(i) new_i = new_i + step

  • If I comment out the i = i + 1 line, the while loop (obviously) runs forever, but the program does not fail.

My question is then, why does Python fail when it hits the while loop on line 5, instead of the actual problem on line 7 (i = i + 1)? Is this some artifact of the interpreter taking the while loop as a block of code?

This piece of code fails in the correct spot:

def populate(maximum, step):
    while i < maximum:
        raise Exception("foo")

Stack trace:

Traceback (most recent call last):
    File "test_python.py", line 12, in <module>
        populate(10, 2)
    File "test_python.py", line 6, in populate
        raise Exception("foo")
Exception: foo

Another note: This only seems to be the case when the variable is used within the start of the control block (ie. while i < maximum). And the same behavior occurs for every type of control block: while, for, if, elif, etc.

Tex4066
  • 337
  • 4
  • 15
  • 4
    This one has nothing to do with immutable vs. mutable. If there’s an assignment to a name that hasn’t been declared `global` *anywhere*, Python creates a variable scoped to the function containing that assignment. – Ry- Dec 19 '18 at 03:50
  • 1
    "Since i is immutable, it can be read while it is out of scope. " No, the mutability of an object has *no affect* on the scoping rules of Python. It is totally irrelevant. – juanpa.arrivillaga Dec 19 '18 at 04:50

1 Answers1

3

Mutability is a red herring here. Mutable values are affected by scope the same way as immutable values. In fact, nothing in the Python language handles mutable values specially (this is a common myth, however).

The key insight is that the scope of a name is fixed in each scope. Within the scope of populate, every name must either be local or global: this decision is a part of the bytecode of the method.

A name that is only read can be looked up from an enclosing scope. But an assignment to a name anywhere within a scope forces that to be treated as a local variable everywhere in the scope. (Unless you use the global or nonlocal keywords.)

So if you're assigning to i anywhere in the method, then i must be a new variable local to the method, and not the global i. If you want i to mean the global i, simply add this line at the top of the method:

global i
Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135
  • Thanks for the response, and for the information about global and local scope. I do know about the `global` keyword and how to make this function work properly. I guess I'm still a little unclear as to why Python is showing the error at the incorrect line though? – Tex4066 Dec 19 '18 at 04:03
  • 1
    It's not incorrect. It's the first line that references `i`. And since we've established that, within this function, `i` is a local variable and not the same thing as the global `i`, you're trying to read it before it has a value. – Daniel Pryden Dec 19 '18 at 04:06
  • I see.. So since it references `i`, even if it is not assigning to it on that line, it is still flagged as the location of the exception. – Tex4066 Dec 19 '18 at 04:14
  • 2
    Right. Assigning to `i` is not an error. Reading a non-existent `i` is an error: an `UnboundLocalError`, to be precise. – Daniel Pryden Dec 19 '18 at 04:17
  • 1
    "In fact, nothing in the Python language handles mutable values specially (this is a common myth, however)." Yes, the amount of misconceptions floating around there regarding this fact is staggering. It's a common misconception that the Python interpreter somehow treats mutable vs immutable objects differently when passed as arguments to a function. – juanpa.arrivillaga Dec 19 '18 at 04:52