Function Return Values and Scope in Python

PythonBeginner
Practice Now

Introduction

In this lab, you will deepen your understanding of functions in Python by exploring their return values and the concept of variable scope. You will begin by examining functions that do not explicitly return a value, observing how they execute actions without producing a result that can be used elsewhere.

Subsequently, you will learn how to add return values to functions, enabling them to produce output that can be captured and utilized in your programs. The lab will then guide you through differentiating between local and global variables, understanding their respective scopes and how they affect variable accessibility within and outside functions. You will also learn how to modify global variables from within functions using the global keyword and explore the concept of nonlocal variables in nested functions.

Explore Functions Without Return Values

In this step, we will explore functions that do not have an explicit return statement. These functions perform an action, such as printing to the console, but do not send back a value to the part of the code that called them.

First, locate the file no_return_function.py in the file explorer on the left side of the WebIDE. Double-click it to open it in the editor.

Add the following code to the no_return_function.py file:

def greet():
    print("Hello, welcome to the world of functions!")

## Call the function
greet()

To run this script, open the integrated terminal by clicking Terminal > New Terminal in the top menu. Then, execute the following command:

python ~/project/no_return_function.py

You will see the function's output printed to the console:

Hello, welcome to the world of functions!

The greet() function executes the print() statement, but it doesn't produce a value that can be stored in a variable. To see what happens when you try to capture the result, modify the no_return_function.py file. Replace the existing content with the following code:

def greet():
    print("Hello, welcome to the world of functions!")

## Call the function and assign its result to a variable
result = greet()

## Print the value of the result
print(f"The result of calling greet() is: {result}")

Save the file and run it again from the terminal:

python ~/project/no_return_function.py

The output will now be:

Hello, welcome to the world of functions!
The result of calling greet() is: None

As you can see, the value of the result variable is None. In Python, any function that does not have an explicit return statement automatically returns None. None is a special value that represents the absence of a value. This confirms that such functions are used for their side effects (like printing) rather than for producing data.

Add Return Values to Functions

In contrast to the previous step, most functions are designed to compute a value and return it to the caller. This is achieved using the return keyword. A return statement immediately exits the function and sends a specified value back.

Open the file return_function.py from the file explorer in your WebIDE.

Add the following code to define a function that adds two numbers and returns their sum:

def add_numbers(a, b):
    """This function adds two numbers and returns the sum."""
    sum_result = a + b
    return sum_result

## Call the function and store the returned value
total = add_numbers(5, 3)

## Print the returned value
print(f"The sum is: {total}")

## Use the returned value in another operation
another_total = add_numbers(10, 20) * 2
print(f"Another calculated value: {another_total}")

Save the file and run the script from the terminal:

python ~/project/return_function.py

You should see the following output, showing that the returned values were successfully captured and used:

The sum is: 8
Another calculated value: 60

A function can return any Python object, including numbers, strings, lists, and even tuples. Returning a tuple is a common way to return multiple values at once.

Add the following code to the end of your return_function.py file to see this in action:

def get_user_info():
    """This function returns user information as a tuple."""
    name = "labex"
    age = 25
    city = "Virtual City"
    return name, age, city

## Call the function and unpack the returned tuple into separate variables
user_name, user_age, user_city = get_user_info()

## Print the unpacked values
print(f"Name: {user_name}, Age: {user_age}, City: {user_city}")

Save the file and run it again:

python ~/project/return_function.py

The complete output will now be:

The sum is: 8
Another calculated value: 60
Name: labex, Age: 25, City: Virtual City

Here, get_user_info() returns three values packaged as a tuple. The calling code then unpacks this tuple into three separate variables, making it easy to work with multiple return values.

Differentiate Local and Global Variables

Understanding variable scope is essential for writing bug-free code. Scope determines where in your program a variable can be accessed.

  • Global Scope: Variables defined outside of any function are global. They can be accessed from anywhere in your script.
  • Local Scope: Variables defined inside a function are local. They can only be accessed from within that function.

Let's explore this concept. Open the variable_scope.py file in the WebIDE.

Add the following code to the file:

## Global variable
global_variable = "I am a global variable"

def my_function():
    ## Local variable
    local_variable = "I am a local variable"
    print(f"Inside the function:")
    print(f"  Accessing global_variable: {global_variable}")
    print(f"  Accessing local_variable: {local_variable}")

## Call the function
my_function()

## Try to access variables outside the function
print(f"\nOutside the function:")
print(f"  Accessing global_variable: {global_variable}")

## Attempting to access local_variable here would cause a NameError
## print(f"  Accessing local_variable: {local_variable}")

Save the file and run it from the terminal:

python ~/project/variable_scope.py

The output demonstrates the accessibility of each variable:

Inside the function:
  Accessing global_variable: I am a global variable
  Accessing local_variable: I am a local variable

Outside the function:
  Accessing global_variable: I am a global variable

The global variable is accessible everywhere, but the local variable is confined to its function. If you were to uncomment the last line, the script would fail with a NameError.

Now, what happens if a local variable has the same name as a global one? The local variable "shadows" the global one, meaning that within the function, the name will refer to the local variable.

Add the following code to the end of your variable_scope.py file to observe this shadowing effect:

## Global variable
my_variable = "I am the global version"

def another_function():
    ## This local variable shadows the global one
    my_variable = "I am the local version"
    print(f"\nInside another_function(): {my_variable}")

## Call the function
another_function()

## The global variable remains unchanged
print(f"Outside another_function(): {my_variable}")

Save and run the script again:

python ~/project/variable_scope.py

The full output will be:

Inside the function:
  Accessing global_variable: I am a global variable
  Accessing local_variable: I am a local variable

Outside the function:
  Accessing global_variable: I am a global variable

Inside another_function(): I am the local version
Outside another_function(): I am the global version

Inside another_function(), my_variable refers to the local version. Outside, it refers to the global version. The assignment inside the function did not affect the global variable.

Modify Global Variables Using the global Keyword

By default, you cannot change the value of a global variable from inside a function. An assignment statement inside a function creates a new local variable. To explicitly modify a global variable, you must use the global keyword.

Open the modify_global.py file in the WebIDE.

Add the following code, which defines a global counter and a function to increment it:

## Global variable
counter = 0

def increment_counter():
    ## Declare that we intend to modify the global counter
    global counter
    counter += 1
    print(f"Inside function: counter is {counter}")

## Print the initial value
print(f"Before calling function: counter is {counter}")

## Call the function to modify the global variable
increment_counter()

## Print the value after the function call
print(f"After calling function: counter is {counter}")

Save the file and run it from the terminal:

python ~/project/modify_global.py

The output shows that the global variable was successfully modified:

Before calling function: counter is 0
Inside function: counter is 1
After calling function: counter is 1

The global counter statement tells Python that any operation on counter within this function should affect the global variable, not a new local one.

To contrast this, let's add another function that doesn't use the global keyword. Add the following code to the end of modify_global.py:

def increment_counter_local():
    ## This assignment creates a new local variable named 'counter'
    counter = 100
    print(f"\nInside function (local): counter is {counter}")

## Print the global counter's value before the test
print(f"Before calling function (local test): counter is {counter}")

## Call the function
increment_counter_local()

## The global counter's value is unaffected
print(f"After calling function (local test): counter is {counter}")

Save and run the script again:

python ~/project/modify_global.py

The complete output demonstrates the difference clearly:

Before calling function: counter is 0
Inside function: counter is 1
After calling function: counter is 1
Before calling function (local test): counter is 1
Inside function (local): counter is 100
After calling function (local test): counter is 1

In the second test, the assignment counter = 100 only affected a local variable within increment_counter_local(). The global counter retained its value of 1.

Understand nonlocal Variables in Nested Functions

Python's scope rules also apply to nested functions (a function defined inside another function). A variable that is not local to the inner function but is local to the outer function is called a "nonlocal" variable.

To modify such a variable from the inner function, you must use the nonlocal keyword. This is similar to how global works, but it applies to nested scopes instead of the global scope.

Open the nonlocal_variable.py file in the WebIDE.

Add the following code to demonstrate the use of nonlocal:

def outer_function():
    outer_variable = "I am in the outer function"

    def inner_function():
        ## Declare that we are modifying the variable from the enclosing scope
        nonlocal outer_variable
        outer_variable = "I have been modified by the inner function"
        print(f"Inside inner_function(): {outer_variable}")

    print(f"Before calling inner_function(): {outer_variable}")
    inner_function()
    print(f"After calling inner_function(): {outer_variable}")

## Call the outer function
outer_function()

Save the file and run it from the terminal:

python ~/project/nonlocal_variable.py

The output shows that the inner function successfully modified the outer function's variable:

Before calling inner_function(): I am in the outer function
Inside inner_function(): I have been modified by the inner function
After calling inner_function(): I have been modified by the inner function

The nonlocal outer_variable statement allows inner_function to rebind the outer_variable from outer_function.

Now, let's see what happens without the nonlocal keyword. Add the following code to the end of nonlocal_variable.py:

def outer_function_local_test():
    outer_variable = "I am in the outer function (local test)"

    def inner_function_local_test():
        ## This assignment creates a new local variable
        outer_variable = "I am a local variable in inner_function"
        print(f"\nInside inner_function_local_test(): {outer_variable}")

    print(f"\nBefore calling inner_function_local_test(): {outer_variable}")
    inner_function_local_test()
    print(f"After calling inner_function_local_test(): {outer_variable}")

## Call the outer function for the local test
outer_function_local_test()

Save and run the script again:

python ~/project/nonlocal_variable.py

The complete output highlights the difference:

Before calling inner_function(): I am in the outer function
Inside inner_function(): I have been modified by the inner function
After calling inner_function(): I have been modified by the inner function

Before calling inner_function_local_test(): I am in the outer function (local test)
Inside inner_function_local_test(): I am a local variable in inner_function
After calling inner_function_local_test(): I am in the outer function (local test)

In the second example, the assignment inside inner_function_local_test() created a new local variable, leaving the outer_variable in the enclosing scope unchanged.

Summary

In this lab, you have explored several key concepts related to Python functions. You started by learning that functions without an explicit return statement implicitly return None. You then practiced using the return keyword to send values back from a function, including returning multiple values as a tuple.

You also delved into variable scope, distinguishing between local variables (accessible only within a function) and global variables (accessible throughout a script). You learned how variable shadowing works and how to use the global keyword to modify a global variable from within a function. Finally, you examined nested functions and used the nonlocal keyword to modify variables in an enclosing, non-global scope.