16 Value Passing, Reference Passing, or How Are Arguments Passed in Python

16 Value Passing, Reference Passing, or How Are Arguments Passed in Python #

Hello, I am Jingxiao.

In the previous first major chapter, we learned the basics of Python functions and their applications together. We roughly understand that passing parameters means passing some arguments from one function to another in order to perform the corresponding tasks. But have you ever wondered how parameter passing works under the hood, and what are the principles behind it?

In actual work, many people may encounter such scenarios: after writing the code, when they test it, they find that the results are different from what they expected. As a result, they start debugging layer by layer. It takes a lot of time, but they only realize in the end that it’s the changes in the data structure during the parameter passing process that caused the “error” in the program.

For example, if I pass a list as a parameter to another function, expecting the list to remain unchanged after the function execution, but often it goes against my wishes. Due to certain operations, its value changes, which can potentially lead to a series of errors in subsequent programs.

Therefore, understanding the parameter passing mechanism in Python is of great significance. It often helps us to write code with fewer mistakes and improve efficiency. Today, let’s learn together how parameters are passed in Python.

What is pass-by-value and pass-by-reference #

If you have been exposed to other programming languages like C/C++, it is easy to understand that there are two common ways of passing parameters: pass-by-value and pass-by-reference. Pass-by-value means copying the value of the parameter and passing it to a new variable in the function. In this way, the original variable and the new variable are independent of each other and do not affect each other.

For example, let’s take a look at the following C++ code:

#include <iostream>
using namespace std;

// Swap the values of two variables
void swap(int x, int y) {
    int temp;
    temp = x;   // Swap the values of x and y
    x = y;
    y = temp;
    return;
}

int main() {
    int a = 1;
    int b = 2;
    cout << "Before swap, value of a: " << a << endl;
    cout << "Before swap, value of b: " << b << endl;
    swap(a, b);
    cout << "After swap, value of a: " << a << endl;
    cout << "After swap, value of b: " << b << endl;
    return 0;
}

In this code, the swap function copies the values of a and b into x and y, and then the values of x and y are swapped. As a result, the values of x and y have changed, but a and b are unaffected, so their values remain unchanged. This is what we call pass-by-value.

Pass-by-reference, on the other hand, usually involves passing the reference of the parameter to a new variable. In this way, the original variable and the new variable will point to the same memory address. If the value of one variable is changed, the other variable will be changed accordingly.

Using the example mentioned earlier, if we modify the swap function to use reference parameters, the code will look like this:

void swap(int& x, int& y) {
    int temp;
    temp = x;   // Swap the values of x and y
    x = y;
    y = temp;
    return;
}

This will produce a different output:

Before swap, value of a: 1
Before swap, value of b: 2
After swap, value of a: 2
After swap, value of b: 1

The values of the original variables a and b have been swapped because pass-by-reference makes a and x, b and y identical. Any change to x and y will inevitably lead to a corresponding change in a and b.

However, these are characteristics of the C/C++ language. So how does parameter passing work in Python? Are they pass-by-value, pass-by-reference, or something else?

Before answering this question, let’s first understand the basic principles of variables and assignment in Python.

Python Variables and Assignments #

Let’s start by looking at the following Python code example:

a = 1
b = a
a = a + 1

First, the value 1 is assigned to the variable a. This means that a is now pointing to the object 1, as shown in the following flowchart:

Next, b = a means that the variable b is also pointing to the object 1. It is important to note that in Python, objects can be referenced or pointed to by multiple variables.

Finally, a = a + 1. It’s worth noting that Python’s data types, such as integers (int), strings (string), etc., are immutable. Therefore, a = a + 1 does not increase the value of a by 1. Instead, it creates a new object with the value 2 and makes a point to it. However, b remains unchanged and still points to the object 1.

As a result, a becomes 2, while b remains 1.

From this example, you can see that, initially, a and b are just two variables that point to the same object, or you can imagine them as two names for the same object. Simply assigning b = a does not create a new object, but allows multiple variables to reference or point to the same object.

Moreover, having two variables point to the same object does not mean that they are bound together. If you reassign one of the variables, it will not affect the value of the other variable.

Now that you understand this basic example of variable assignment, let’s take a look at an example with a list:

l1 = [1, 2, 3]
l2 = l1
l1.append(4)
l1
[1, 2, 3, 4]
l2
[1, 2, 3, 4]

Similarly, we first make both l1 and l2 point to the object [1, 2, 3].

Since lists are mutable, l1.append(4) does not create a new list. Instead, it inserts the element 4 at the end of the original list, resulting in [1, 2, 3, 4]. Since l1 and l2 both point to this list, any changes to the list will be reflected in both l1 and l2. Therefore, the values of l1 and l2 become [1, 2, 3, 4].

Additionally, it is important to note that variables can be deleted in Python, but objects cannot. For example, consider the following code:

l = [1, 2, 3]
del l

del l deletes the variable l, and you will no longer be able to access l. However, the object [1, 2, 3] still exists. Python’s built-in garbage collection system tracks references to each object during program execution. If [1, 2, 3] is referenced elsewhere besides l, it will not be garbage collected; otherwise, it will be.

Therefore, in Python:

  • Variable assignment means that a variable is simply pointing to an object, and it does not imply copying the object to the variable. An object can be referenced by multiple variables.
  • Changes to mutable objects (lists, dictionaries, sets, etc.) will affect all variables that point to the object.
  • For immutable objects (strings, integers, tuples, etc.), all variables that point to the object will always have the same value and will not change. However, certain operations (such as +=) will return a new object when updating the value of an immutable object.
  • Variables can be deleted, but objects cannot.

Python Function Parameter Passing #

From the above explanation of the principles of variable naming and assignment in Python, I believe you can generalize and guess how parameters are passed in Python functions, right?

Here, I will first quote a passage from the official Python documentation:

“Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.”

To be accurate, Python’s parameter passing is pass-by-assignment, or also known as pass-by-object-reference. Since all data types in Python are objects, when passing parameters, we are actually making the new variable point to the same object as the original variable, and there is no concept of pass-by-value or pass-by-reference.

For example, let’s take a look at the following example:

def my_func1(b):
    b = 2

a = 1
my_func1(a)
a

In this example, the parameter passing makes the variables a and b both point to the object 1. However, when we execute b = 2, the system will create a new object with the value 2 and make b point to it, while a still points to the object 1. Therefore, the value of a remains unchanged and still equals 1.

So, does that mean we cannot change the value of a in the above example?

The answer is definitely no. We just need to make a slight change and let the function return the new variable and assign it to a. In this way, a will point to a new object with the value 2, and the value of a will become 2.

def my_func2(b):
    b = 2
    return b

a = 1
a = my_func2(a)
a

However, when a mutable object is passed as a parameter into a function and its value is changed, it will affect all variables that point to it. For example, consider the following example:

def my_func3(l2):
    l2.append(4)

l1 = [1, 2, 3]
my_func3(l1)
l1

Here, l1 and l2 initially both point to the list [1, 2, 3]. However, because lists are mutable, when the function append() is executed to add a new element 4 to the end, the values of variables l1 and l2 are both changed accordingly.

But in the following example, although it seems like a new element is added to the list in both cases, the results are obviously different.

def my_func4(l2):
    l2 = l2 + [4]

l1 = [1, 2, 3]
my_func4(l1)
l1

Why is l1 still [1, 2, 3] instead of [1, 2, 3, 4]?

It is important to note that l2 = l2 + [4] creates a new list with the element 4 added to the end, and makes l2 point to this new object. This process is independent of l1, so the value of l1 remains unchanged. Of course, if we want to change the value of l1, we need to let the above function return a new list and assign it to l1:

def my_func5(l2):
    l2 = l2 + [4]
    return l2

l1 = [1, 2, 3]
l1 = my_func5(l1)
l1

Here, what you need to remember is the difference between changing the variable and reassignment:

  • In my_func3(), the value of the object is changed directly, so after the function returns, all variables that point to this object will be modified.
  • But in my_func4(), a new object is created and assigned to a local variable, so the original variable remains unchanged.

As for the usage of my_func3() and my_func5(), although they have different syntax, they provide the same functionality. However, in practical applications, we tend to prefer the syntax of my_func5() with return statements, as it is more concise, clear, and less prone to errors.

Summary #

Today, we learned about the basic principles of variables and assignment in Python, and explained how arguments are passed in Python. Unlike other languages, parameter passing in Python is neither pass-by-value nor pass-by-reference; it is called pass-by-assignment, or object reference passing.

It is important to note that here, assignment or object reference passing does not refer to a specific memory address, but to a specific object.

  • If the object is mutable, when it changes, all variables that point to this object will change.
  • If the object is immutable, simple assignment can only change the value of one variable, while the other variables remain unaffected.

With this in mind, if you want to change the value of a variable through a function, there are usually two methods. One is to directly pass the mutable data type (such as lists, dictionaries, sets) as a parameter and modify it directly. The other is to create a new variable to store the modified value, and then return it to the original variable. In practical work, we prefer the latter because it is clear and less prone to errors.

Discussion Questions #

Lastly, I’ve left you with two discussion questions.

The first question is, do l1, l2, and l3 all point to the same object in the code below?

l1 = [1, 2, 3]
l2 = [1, 2, 3]
l3 = l2

The second question is, what is the final output when printing d in the code below?

def func(d):
    d['a'] = 10
    d['b'] = 20

d = {'a': 1, 'b': 2}
func(d)
print(d)

Feel free to leave a comment and share your thoughts. You’re also welcome to share this article with your colleagues and friends to enhance communication and progress together.