Reputation: 103
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
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
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".
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.
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.
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"
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