What is a side-effect of a function in Python?
Tags: python, beginners
Published: 2020-05-30 00:58:36

Any meaningful function or procedure needs some sort of data from its calling environment to produce meaningful results. What happens if the data sent to the function gets changed within the function? A function is said to have a  **side-effect** if the supplied values or anything in function's environment like global variable gets updated within the function. This article attempts to explain the concept of  **mutability** or **side effect** of values passed to a function or procedure in Python. 

Let’s start with a **few fundamental terms** before we attempt to answer Python’s side-effect behavior.

<h4>What are parameters?</h4>
Data that gets passed to a function as input are called **parameters**. **num1** and **num2** in the below function are parameters. 
```python
def sum(num1, num2):
    return num1 + num2
```

<h4>What are arguments?</h4>
Values that are supplied during function invocation are called **arguments**. So **val1** and **val2** in the below function call are arguments.  Although many use these terms interchangeably.  
```python
val1 = 10
val2 = 20
ans = sum(val1, val2)
```

<h4>What are passing arguments by value and reference?</h4>
If a **new copy** of arguments are made during a function call for the supplied parameter, then arguments are said to be **passed by value**. If a **reference** of the same variable is passed to a function then arguments are **passed by reference**.  

<h4>Are the arguments passed by value or by reference in Python?</h4>
Python uses the mechanism pass arguments by sharing object reference during function calls. Let’s examine Python’s behavior using below function example:
```python
def ref_copy_demo(x):
    print(f"x = {x}, id = {id(x)}")
    x += 45
    print(f"x = {x}, id = {id(x)}")
    
    
num = 10
print(f"before function call - num = {num}, id = {id(num)}") 
ref_copy_demo(num)
print(f"after function call - num = {num}, id = {id(num)}")

# Output 
before function call - num = 10, id = 140704100632512
x = 10, id = 140704100632512
x = 55, id = 140704100633952
after function call - num = 10, id = 140704100632512
``` 
Here is the illustration of above function call:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6056x8a7rwk1hv9h4oot.png)

Let’s analyze the function call and its output:

* We have used the **id** function in the above call to get the identity value of the object.  Id function returns an integer which is unique and constant for this object during its lifetime. In the above context, it helps us in tracking whether the same object is passed by reference to the function.
* Please note that the id function’s value has changed for the parameter passed before and after variable value change.
* So this means the parameter inside the function remains the same as the argument until there is no change in parameter’s value. Python keeps the reference of the argument passed. But as soon the parameter gets updated, a  local copy of the parameter is made leaving the argument value unchanged. 

<h4>What is a side effect?</h4>
Function is said to have a side effect if it changes anything outside of its function definition like changing arguments passed to the function or changing a global variable. For example:
```python
def fn_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits += ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")
 
fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")

# Output 
Fruits List before function call - ['apple', 'orange'] id - 1904767477056
Fruits before change - ['apple', 'orange'] id - 1904767477056
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 1904767477056
Fruits List after function call - ['apple', 'orange', 'pear', 'banana'] id - 1904767477056
```
So this function clearly has side effect due to below reasons:

* Id value argument and parameter are exactly the same. 
* Argument has additional values added after the function call.    

<h4>How to create a similar function without side effect?</h4>

```python
def fn_no_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits = fruits + ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")
 
fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_no_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")

# output
Fruits List before function call - ['apple', 'orange'] id - 2611623765504
Fruits before change - ['apple', 'orange'] id - 2611623765504
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 2611625160320
Fruits List after function call - ['apple', 'orange'] id - 2611623765504
```
With explicit call to the assignment during fruits list update, list value changed only within the function as it supposed to be. So this function has no side effect. 

Please note we could have also avoided side-effect in the first function call if we would have explicitly made a copy of the list - `fn_side_effects(fruit_list[:])`

<h4>Why is it important to write functions without side-effects?</h4>
* Functions with side effects especially when it is unintended could lead to a lot of potential bugs which are harder to debug.
* It is easier to write tests for functions with no side-effects. 
* If the function is supposed to change anything in the environment, it must be clearly documented to avoid confusion.
* Care must be taken in writing function definitions containing mutable data types (like Lists, Sets, Dictionary etc…) as their function parameter.