WNavidson1
WNavidson1

Reputation: 103

python variable referenced before assignment int vs array

When I compile this snippet with func1 I get an error about referencing output before assignment, which seems reasonable to me:

def func1():
    output += 1

output = 0
func1()
print(output)    

But when I compile this snippet with func2 I don't get an error, which seems unreasonable to me.

def func2():
    output.append(1)

output = []
func2()
print(output)

Thoughts? Thanks in advance and sorry if this is a duplicate. I didn't didn't see this particular question addressed in similarly titled posts when researching.

Upvotes: 1

Views: 484

Answers (2)

Will Vousden
Will Vousden

Reputation: 33358

The problem here is in how Python binds names to variables.

Any time you write an assignment in a function (something like a = b or a += b), Python will bind that name locally for the entire function scope. This means that any variables with that name declared outside the function are ignored.

For example:

a = 1 # This variable is ignored.
def foo():
    print(a) # "a" hasn't been defined yet!
    a = 2 # This causes "a" to bind to a local variable.

This will produce an UnboundLocalError at the print statement because Python sees that a is assigned to later in the function, and binds it locally, ignoring the variable you defined outside the function. Since a is only defined after the print statement, you get an error.

This can be very confusing because removing the a += 2 line will cause the print statement to work as expected!

The solution is to explicitly tell Python not to bind the name locally. This can be done with the global and nonlocal keywords, e.g.:

a = 1
def foo():
    nonlocal a # or: global a
    print(a)
    a += 2

global tells Python to bind the name in the module's global scope, while nonlocal (introduced in Python 3) tells Python to bind to the enclosing scope (for example, if you define a function inside a function).

There's a nice explanation of this (with examples) in the documentation :-)


Once we understand these rules, we can see that your first example fails because you're trying to increment a variable that doesn't yet exist. output += 1 is equivalent to output = output + 1, which triggers local name binding.

On the other hand, your second example doesn't trigger the error because you're not assigning to output (you're mutating it instead), so it will bind to the global variable that you defined.

Upvotes: 1

Rahul Bharadwaj
Rahul Bharadwaj

Reputation: 2784

In Python, global variables when called in functions don't work all the same.

The data types (on a high level) in Python can be categorised into "mutable" and "immutable".

  • If you have have mutable datatypes like list then they can be both : accessed and modified in the function without referring them with a global keyword.

Eg:

l = []
def foo():
    l.append(1) #works

foo()
print(l) # prints [1], so foo() changed the global list l.
  • But if you have immutable types like int or str then they can be accessed but not modified.

Eg:

someString = "abcdef"

def foo():
    print(someString[2]) # prints "c"
    someString += "l"    # error as someString cannot be modified unless you include it in the function foo() with the global keyword as shown below.
  • But to modify a global variable of immutable types like str you have to include it first with the global keyword then you're free to use it like any local variable.

Eg:

someString = "abcdef"

def foo():
    global someString    # This now includes someString in foo() scope and is allowed to be modified.

    print(someString[2]) # prints "c"

    someString += "l"    # works as expected

    print(someString) # prints "abcdefl"
  • Thanks to Mark Tolonen for this valuable point. At the end, you cannot reassign a global variable without the global keyword, irrespective of whether the variable is mutable or immutable.

Eg:

someInt  = 1
someStr  = "abc"
someList = [1,2,3]

def foo():
    someInt  += 3     # error, local variable referenced before assignment.
    someStr  += "def" # error, local variable referenced before assignment.
    someList += [4]   # error,  local variable referenced before assignment. Note that this is **not** the same as doing someList.append(4).

To make the above work, include them in the function via global keyword and use them as required.

Upvotes: 0

Related Questions