Scope
A valuable aspect of the “encapsulation” provided by functions is that the function’s input argument variables, and any variables defined within the function, cannot be “seen” nor accessed outside of the function. That is, these variables are said to have a restricted scope.
Definition:
The scope of a variable refers to the context in which that variable is visible/accessible to the Python interpreter.
Until our work with comprehension-statements and functions, we had only encountered variables that have file scope. This means that a variable, once defined, is visible to all parts of the code contained in the same file. Variables with file scope can even be accessed within functions. By contrast, the variables defined within a function or as input arguments to a function have a restricted scope - they can only be accessed within the context of the function:
x = 3 # `x` has file scope. It can be even be accessed
# within a function, even if it isn't passed to
# the function as an argument
# `my_func` has file scope (after it is defined)
def my_func(y):
func_var = 9 + x # `x` will have the value 3
# the scope of `y` and `func_var` is restricted to this function
return y
# `func_var` and `y` do not exist here
print(func_var) # raises NameError: name `func_var` not defined
print(y) # raises NameError: name `y` not defined
Python’s scoping rules are quite liberal compared to those of other languages, like C++. In most situations, Python will give variables file scope. Let’s briefly survey the various contexts in which variables are defined, along with their corresponding scoping rules. Assume that the following code represents the entire contents of the Python script “example_scope.py”:
# this demonstrates scope of variables in different contexts
# nothing meaningful is computed in this file
from itertools import combinations # `combinations` has file scope
# `my_func` has file scope
# `in_arg1` has restricted scope
# `in_arg2` has restricted scope
# `func_block` has restricted scope
def my_func(in_arg1, in_arg2="cat"):
func_block = 1
return None
# `file_var` has file scope
# `comp_var` has restricted scope
file_var = [comp_var**2 for comp_var in [-1, -2]]
# `if_block` has file scope
if True:
if_block = 2
else:
if_block = 3
# `it_var` has file scope
# `for_block` has file scope
for it_var in [1, 2, 3]:
for_block = 1
# `while_block` has file scope
while True:
while_block = None
break
In the preceding code, the following variables have file scope:
combinations
my_func
file_var
if_block
it_var
for_block
while_block
whereas the following variables have restricted scope:
in_arg1
in_arg2
func_block
comp_var
In C++, the variables if_block
, it_var
, for_block
, and while_block
all would have had restricted scopes - these variables would not be defined outside of their respective if/for/while blocks.
Takeaway:
Variables defined within a function have a restricted scope such that they do not exist outside of that function. Most other contexts for defining variables in Python produce variables with file scope (i.e. they can be accessed anywhere in the file’s code, subsequent to their definition).
Variable Shadowing
What happens when a file-scope variable and a function-scope variable share the same name? This type of circumstance is known as variable shadowing. Python resolves this by giving precedence to the variable with the most restricted scope, when inside that scope:
x = 2
y = 3
def func(x):
# input-arg `x` overrides file-scope version of `x`
y = 5 # overrides file-scope version of `y`
return x + y
# `x` is 2 here, once again
# `y` is 3 here, once again
print(func(-5)) # prints 0
print(x, y) # prints 2 3
and similarly,
it = "cow"
def func():
it = "dog" # overrides file-scope version of `it`
my_list = [it**2 for it in [1, 2, 3]] # within the list comprehension, the func-scope `it` is overridden
# `it` is "dog" here, once again
return None
# `it` is "cow" here, once again