Variable Scope#

New coders often understand variables and functions, but often miss learning about the concept of scope - particularly what happens when simple types are passed as paramters to functions.

Example 1#

The simple code below attempts to double the value of the my_salary variable using the function to_double but FAILS. Take a look at the listing and its output.

def double_value(to_double):
    print('double_value() has been called')
    to_double = to_double * 2 
    print(f'Inside double_value() to_double now = {to_double}')    
    
def main():
    my_salary = 25_000
    print(f'value of my_salary BEFORE calling function {my_salary}')
    #call the function and pass in keyword argument
    double_value(to_double=my_salary)
    print(f'value of my_salary AFTER calling function {my_salary}')
    
if __name__ == '__main__':
    main()
value of my_salary BEFORE calling function 25000
double_value() has been called
Inside double_value() to_double now = 50000
value of my_salary AFTER calling function 25000

We get the negative result because within double_value() the argument to_double is called a local variable with scope local to the function. Local scope means thatto_double only happen within the function. You can think of to_double as a copy of my_salary when it is passed in (because it is).

Side note: reasons for confusion with function scope.#

New coders sometimes get confused and frustrated about scope when code is written as follows:

def double_value(salary):
    print('double_value() has been called')
    salary = salary * 2 
    print(f'Inside double_value() salary now = {salary}')    
    
def main():
    salary = 25_000
    print(f'value of salary BEFORE calling function {salary}')
    #call the function and pass in keyword argument
    double_value(salary)
    print(f'value of salary AFTER calling function {salary}')
    
if __name__ == '__main__':
    main()
value of salary BEFORE calling function 25000
double_value() has been called
Inside double_value() salary now = 50000
value of salary AFTER calling function 25000

There is nothing techincally wrong with the above code, but the dual naming of salary often leads to confusion with coders new to the concept of scope. In my first example, I was careful to give a generic name to the parameter in double_value() and a specific name to the variable in the calling function (it was my salary).

Frustrations with the above code often leads to new coders resorting to python’s global keyword. There’s typically no need to use global varibles in this way and if you are then I’d advise a rethink of your code!

Example 2#

If for example, you intended to double the value of my_salary, a more suitable approach to double the value would be for a function to return a value e.g.

def double_value_with_return(to_double):
    # this time we return the doubled value
    # this means that we do not have scope problems
    # because the calling function is returned a new variable
    return to_double * 2 
    
def main():
    my_salary = 25_000
    print('value of my_salary BEFORE calling function {}'.format(my_salary))
    #call the function and pass in keyword argument
    my_salary = double_value_with_return(to_double=my_salary)
    print('value of my_salary AFTER calling function {}'.format(my_salary))
    
if __name__ == '__main__':
    main()
value of my_salary BEFORE calling function 25000
value of my_salary AFTER calling function 50000

Module, Function, and Notebook scope#

It’s worth noting that your variables can have scope at different levels within your program/code. For example, if you create a python module that includes variable that you are going to effectively treat as constants then any function or class within the module can use them directly. A similar pattern can be applied within a Jupyter notebook. In fact, because notebooks usually contain lots of scripting you usually have a lot of variables with notebook level scope (this can occationally lead to mistakes and bugs - be careful!).