Variable scope "leakage"
Compared to most other languages, python's variable scopes are very weird. If you are coming from other languages like java, c#, c++ etc, then this is going to lead to unintentional bugs.
Introduction
Does the following program throw an error? Or does it print hello?
if True:
var = "hello"
if True:
print(var)
The answer is: It prints hello.
If this caught you by surprise, then this note is for you. If you are coming from other languages like java, c#, c++ etc, then this is something that is different in python. It can trip you up especially if you're a polygot.
There are 2 major reasons why polygots cause bugs in python projects:
- Not understanding scope of variables in python
- Not understanding the python assignment model
This article focuses on point 1 - Not understanding scope of variables in python.
You don't need to declare variable in the outer scope before setting its value or accessing it:
is_available = None # no need to decalre the variable here
if(<your condition>):
is_available = True
else:
is_available = False
print(is_available)
Instead, you can just do this:
if(<your_condition>):
is_available = True
else:
is_available = False
print(is_available) # variable is still accessible here!
In python, the variables "leak" into the outer scopes.
Note: for mutual sanity, I recommend declaring variable first in the outermost possible scope, before you go ahead and use it. Keeps things easier to reason with.
Explanation
We can put it in one line:
**Variables are scoped to the nearest function, class, module.
In this exact order.**
Now, again let's look at an example. What's the output of the program below?
def fn():
if True:
variable_in_if =1234
print("Value inside if is:", variable_in_if)
# still accessible, outside the if
print("Value outside if is:", variable_in_if)
fn()
Again, coming from other languages, you may expect the output to be:
Value inside if is: 1234
Value outside if is: None (or maybe some error at this live)
But in python, the actual output is:
Value inside if is: 1234
Value outside if is: 1234
The variable_in_if is accessible outside the if also, as if it leaked out.
Now we know for sure - variables "leak" out in python. This isn't an anomaly. This is a normal behaviour in python.
The question you should be asking is: To what extent does a variable leak? What is the scope of this leak?
Answer - Variables are scoped to the nearest function, class, module. In this exact order.
In the above example, the variable_in_if was scoped to the nearest function, which was example_function(). So once it got declared and assigned a value, it became accessible from everywhere throughout the function.
Here is another example. This time, the if that declares the variable is nested inside another if:
def example_function():
if True:
if True: # nested if
var = "hello"
print(var) # var is still accissible outside the nested if
example_function() # prints hello
The scope of the variable var was set to the nearest function - example_function(). It is accessible throughout the function. Doesn't matter how much nesting is done, the variable will leak into the scope of example_function().
Examples to drill it down
Scope within a function
def outer_function():
variable_of_outer_function = 1234
def inner_function():
print(variable_of_outer_function)
return
return
Here, variable_of_outer_function is scoped to the nearest function, which is outer_function. So it is available everywhere inside the outer function. So it is also accessible inside inner_function().
Scope in nested functions
def outer_function():
def inner_function():
variable_of_inner_function = 1234
return
print(variable_of_inner_function) # accessible within inner_function
print(variable_of_inner_function) # NOT accessible in outer_function
return
Here, variable_of_inner_function is scoped to the nearest parent function inner_function. So it is not accessible to the print statement of outer_function.
Global vs local scope collision
variable = 1234
def the_function():
variable = 6789
print(variable)
In this case there is a global variable and a local variable within the function.
Here, python treats these two as different variables, and preserves the value of both of the. The local variable does not override the value of the global variable.
So, when python tries to fetch the value of variable to print(), it gets 2 possible values: global and local. In this case, python will:
Look for the variable in the current scope. If variable isn't found in current scope, it will go one scope higher and look for the variable. It will keep going higher and higher in scope until it finds a variable of the same name.
If it finds the variable, it will print that value. If it doesn't find any variable, it will raise an exception.
In this case, python is finding value for the print() in the function's local scope, and will find variable right there in the function's local scope. So it will use that local scope value and print 6789 . It didn't have to go up to a higher scope to find the variable.
In this example below, it won't find variable in the function's scope. So it will go up and find variable in the global scope. So python will print 1234, which is the global scope's variable's value.
variable = 1234
def the_function():
print(variable)