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.
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:
whereas the following variables have restricted scope:
In C++, the variables
while_block all would have had restricted scopes - these variables
would not be defined outside of their respective if/for/while blocks.
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).
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
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