In this article we'll be taking a look at Global and Non-Local Variables in Python and how you to use them to avoid issues when writing code.
We'll be starting off with a brief primer on variable scopes before we launch into the how and why of using global and non-local variables in your own functions.
Scopes in Python
Before we can get started, we first have to touch on scopes. For those of you who are less familiar, "scope" refers to the context in which a variable is defined and how it can be accessed or altered or more specifically - from where it can be accessed.
And in programming, like in life, context is important.
By referencing Python right now, you can infer from context that I am referring to the programming language. In another context however, Python could be a reference to a snake, or a comedic group.
Global and local scopes are how your program understands the context of the variable that you are referencing.
As a rule, variables defined within a function or class (as an instance variable) are local by default, and those outside of functions and classes are global by default.
Local Variables in Python
With that understood, let's see it in action. We'll start by defining a function with it's own local variable inside. In this function we have the variable
fruit, which we initialize as a list and print:
def shopping_list(): fruit = ['apple', 'banana'] print(fruit) shopping_list()
And as expected, this works like a charm:
But what happens when we move the print statement outside of the function?
def shopping_list(): fruit = ['apple', 'banana'] shopping_list() print(fruit)
We get an error"
Traceback (most recent call last): File "<string>", line 5, in <module> NameError: name 'fruit' is not defined
NameError, as the fruit was defined locally and therefore remains confined to that context.
In order for our program to understand the variable globally (outside of the function), we need to define it globally.
Global Variables in Python
What if instead of initially defining our variable within the function, we move it outside and initialize it there?
In this case, we can reference it outside of the function and everything works.
But if we try to redefine the fruit variable inside
shopping_list, those changes won't update to the original global variable, instead being isolated locally:
fruit = ['apple', 'banana'] def shopping_list(): fruit = ['apple', 'banana', 'grapes'] shopping_list() print(fruit)
This is because the
fruit we've modified in the
shopping_list() function is a new, local variable. We've created it, assigned a value to it, and done nothing after that. It's effectively fully redundant code. The
print() statement prints the value of the global variable that's in scope for it.
The global Keyword
If we want those changes to be reflected in our global variable, instead of making a new local one, all we have to do is add the
global keyword. This allows us to communicate that the
fruit variable is indeed a global variable:
fruit = ['pineapple', 'grapes'] def shopping_list(): global fruit fruit = ['pineapple', 'grapes', 'apple', 'banana'] shopping_list() print(fruit)
And sure enough, the global variable is modified with the new values, so one we call
print(fruit), the new values are printed:
['pineapple', 'grapes', 'apple', 'banana']
By defining the context of the fruit variable we're referring to as the global one, we can then redefine and alter it to our hearts content knowing that the changes we make within the function will be carried over.
We could also define a global variable within our function and have it be able to be referenced and accessed anywhere else.
def shopping_list(): global fruit fruit = ['pineapple', 'grapes', 'apple', 'banana'] shopping_list() print(fruit)
This would output:
['pineapple', 'grapes', 'apple', 'banana']
We could even declare a global variable within one function and access it in another without specifying it as global in the second one:
def shopping_list(): global fruit fruit = ['pineapple', 'grapes', 'apple', 'banana'] def print_list(): print(fruit) shopping_list() print(fruit) print_list()
This results in:
['pineapple', 'grapes', 'apple', 'banana'] ['pineapple', 'grapes', 'apple', 'banana']
Caution when Using Global Variables
While being able to modify a global variable locally is a handy little tool to have, you have to treat it with a fair bit of caution. Overzealous rewriting and overriding of scope is a recipe for disaster that ends with bugs and unexpected behavior.
It's always important to make sure that you are manipulating a variable only in the context you need it, and otherwise leaving it alone, this is the main drive behind the principle of encapsulation.
We'll take a quick look at an example of a potential issue before we move on to some of the ways that global variables can be useful in your own code:
fruit = ['pineapple', 'grapes', 'apple', 'banana'] def first_item(): global fruit fruit = fruit def iterate(): global fruit for entry in fruit: print(entry) iterate() print(fruit) first_item() print(fruit)
Running the code above, we get the following output:
pineapple grapes apple banana ['pineapple', 'grapes', 'apple', 'banana'] pineapple
In this example we reference the variable in both functions,
iterate(). Everything seems works fine if we call
iterate() and then
If we reverse that order or attempt to iterate after, we run into a big issue:
first_item() print(fruit) iterate() print(fruit)
This now outputs:
pineapple p i n e a p p l e pineapple
fruit is now a string that will be iterated through. What's worse is that this bug won't present itself until it's presumably too late. The first code ran seemingly fine.
Now, this problem is obvious on purpose. We tampered with a global variable directly - lo and behold, it's changed. However, in more complex structures, one might accidentally take global variable modification a step too far and get unexpected results.
The nonlocal Keyword
Just because you need to be cautious doesn't mean that global variables aren't also incredibly useful. Global variables can be helpful whenever you want to update a variable without providing it in the return statement, like a counter. They are also very handy with nested functions.
For those of you using Python 3+, you can make use of
nonlocal, a keyword which functions very similarly to
global, but primarily takes effect when nested in methods.
nonlocal essentially forms an in-between of global and local scope.
Since we've been using shopping lists and fruits for most of our examples, we could think of a checkout function that adds up the total of the purchases:
def shopping_bill(promo=False): items_prices = [10, 5, 20, 2, 8] pct_off = 0 def half_off(): nonlocal pct_off pct_off = .50 if promo: half_off() total = sum(items_prices) - (sum(items_prices) * pct_off) print(total) shopping_bill(True)
Running the code above, we get the output:
This way the global count variable is still local to the outer function, and will not impair (or exist) at a higher level. This gives you some latitude in adding modifiers to your functions.
You can always confirm this by trying to print
pct_off outside of the shopping bill method:
NameError: name 'pct_off' is not defined
If we had used the
global keyword instead of the
nonlocal keyword, printing
pct_off would result in:
At the end of the day, global (and nonlocal) keywords are a tool, and when used properly can open up a lot of possibilities for your code. I personally use both of these keywords rather frequently in my own code, and with enough practice you'll come to see how powerful and useful they really can be.
As always, thank you so much for reading and Happy Hacking!