Skip to content

A Quick explanation of side_effect in Python

In the world of Python programming, understanding and appropriately managing side effects is vital. These often overlooked, but critical, aspects can be the difference between a well-functioning program and one that's unpredictable and hard to maintain.

Side effects in Python occur when a function, instead of just returning a value, also modifies some state or has an observable interaction with the outside world. While not always negative, these side effects can introduce bugs that are difficult to trace and fix. Therefore, learning to control or avoid them can significantly improve the stability and reliability of your code.

Want to quickly create Data Visualization from Python Pandas Dataframe with No code?

PyGWalker is a Python library for Exploratory Data Analysis with Visualization. PyGWalker (opens in a new tab) can simplify your Jupyter Notebook data analysis and data visualization workflow, by turning your pandas dataframe (and polars dataframe) into a tableau-alternative User Interface for visual exploration.

PyGWalker for Data visualization (opens in a new tab)

What is a Side Effect in Python?

In Python, a side effect is any change that a function makes to its state or the global program state, besides its return value. Side effects can include modifying a global or static variable, changing the original object, producing console output, or writing to a file or a database. They're commonly seen in operations such as I/O operations, updates to global states, or even within class methods that alter the object state.

# Python side effect example
def add_element(data, element):
    data.append(element)
    return data
 
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3, 4]

In this add_element function, we have a side effect. The function is not only returning the updated list but also modifying the original my_list. This is a typical Python side effect example, and one of the top side effect mistakes to avoid when writing Python code.

Pure Functions in Python

A pure function, by contrast, is a function that, for the same input, will always produce the same output and has no side effects. Hence, the purity of a function is its self-contained nature, making it predictable and easy to test.

# Python pure function example
def add_element_pure(data, element):
    new_list = data.copy()
    new_list.append(element)
    return new_list
 
my_list = [1, 2, 3]
print(add_element_pure(my_list, 4))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3]

In this example, add_element_pure is a pure function. It creates a new list based on the input, and then appends the new element to this list. The original my_list remains unchanged. Understanding the characteristics of pure functions in Python is crucial for writing code that's robust and easier to debug.

Using Decorators to Control Side Effects

Decorators in Python offer a powerful way to modify the behavior of a function or class. By wrapping the function or class, a decorator can execute code before or after the wrapped function runs, without changing its source code.

# Python decorator example
def no_side_effects_decorator(func):
    def wrapper(*args, **kwargs):
        data_copy = args[0].copy()  # create a copy of the data
        return func(data_copy, *args[1:], **kwargs)
    return wrapper
 
@no_side_effects_decorator
def add_element(data, element):
    data
 
.append(element)
    return data
 
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3]

In the code snippet above, the decorator no_side_effects_decorator wraps the add_element function. It ensures that a copy of the data is used for operations, leaving the original data untouched. This is a way how to use decorators in Python to control side effects.

Testing for Side Effects Using Mock Objects

The unittest.mock library in Python is a powerful tool for writing tests. It allows you to replace parts of your system under test and make assertions about how they've been used. The unittest.mock library also provides a Mock class that you can use to create a mock object.

In Python unit testing, one common use of mock objects is to test the behavior of code under controlled conditions. For example, when a function calls another function (which may have side effects), a mock object can simulate the called function, preventing the side effects.

from unittest.mock import Mock
 
# Original function that we will mock
def add_element(data, element):
    data.append(element)
    return data
 
# Mock object
add_element = Mock(side_effect=lambda data, element: data + [element])
 
# Test with the mock object
my_list = [1, 2, 3]
print(add_element(my_list, 4))  # Output: [1, 2, 3, 4]
print(my_list)  # Output: [1, 2, 3]

In this example, add_element is a function that appends an element to a list, causing a side effect. But when we replace add_element with a mock object, it simulates the function without the side effect. This is an example of using mock objects in Python to control side effects.

The unittest.mock library also provides the patch function, which you can use to replace the real objects in your code with Mock instances during testing. The 'patch' is then undone automatically after the test is finished.

In the next part of the article, we'll discuss more about the patch library in Python and explore more Python mock object examples. We'll also delve deeper into the characteristics of pure functions in Python, and we'll look at more Python decorator examples to control side effects. Stay tuned to learn how to use patch in Python and how to avoid common mistakes when dealing with side effects in Python.

Using the Patch Library in Python

The patch library in Python, specifically unittest.mock.patch, allows you to control the scope of the mock and determine when the original object is replaced and when it's restored. It's particularly useful for unit tests where you want to mock the behavior of an object during a test and then have the mock automatically removed after the test is completed.

from unittest.mock import patch
 
def add_element(data, element):
    data.append(element)
    return data
 
def test_add_element():
    with patch('__main__.add_element', side_effect=lambda data, element: data + [element]):
        my_list = [1, 2, 3]
        print(add_element(my_list, 4))  # Output: [1, 2, 3, 4]
        print(my_list)  # Output: [1, 2, 3]
 
test_add_element()

In this code snippet, the add_element function is temporarily replaced within the scope of the with statement. After executing the test function, the original add_element is restored.

Immutable Data Structures and Side Effects

Immutable data structures are another tool to help control side effects in Python. By definition, an immutable object can't be changed after it's created. In Python, examples of immutable data structures include tuples, strings, and frozensets.

When using immutable data structures, any operation that modifies the data will instead create a new object. This helps to avoid side effects since the original data remains unmodified.

# Tuple in Python is immutable
my_tuple = (1, 2, 3)
new_tuple = my_tuple + (4,)  # Create a new tuple
print(new_tuple)  # Output: (1, 2, 3, 4)
print(my_tuple)  # Output: (1, 2, 3)

In this example, my_tuple is a tuple, which is an immutable data structure in Python. When we try to add an element to it, a new tuple new_tuple is created, and my_tuple remains unchanged.

Conclusion

Managing side effects in Python is an important aspect of writing high-quality, maintainable code. By understanding and leveraging the concepts of pure functions, decorators, and immutable data structures, as well as mastering the use of the mock object and patch libraries, you can keep your Python code free of unnecessary side effects. This not only makes your code more reliable but also easier to test and debug, resulting in higher quality Python applications.

FAQ

Let's now tackle some frequently asked questions:

  1. What is a side effect in Python?
    A side effect in Python is any change that a function makes to the state of the program or to the function's own state, apart from the value it returns. Side effects can include altering global or static variables, modifying the original object, producing console output, or writing to a file or database.

  2. Why should you avoid side effects in Python?
    Side effects in Python, while not always harmful, can introduce bugs that are difficult to trace and fix. They can make your code unpredictable and hard to reason about. By avoiding side effects, you make your code more stable, predictable, and easier to test.

  3. What is a pure function in Python?
    A pure function in Python is a function that always produces the same output for the same input and has no side effects. Pure functions are self-contained, making them predictable and easy to test.