Understanding Pass by Value vs Pass by Reference in Python: Explained

Learning is a weird process, right? There is not one size fits all when it comes to learning. Some people learn better when they are reading, some people when they are listening, some need visual cues, some memorize things as easily as they change clothes.

With coding is no different. Some people learn by doing. Some need the theory. In this journey of learning how to code I learned A LOT about the way I learn and the way I don’t.

Growing up I was never the type of person that used to sit for hours studying. I used to learn in the classroom and briefly study the day before the test and when the subject was not interesting enough, or I didn’t like the teacher I struggled. So I was really bad in math until a got a teacher that was amazing and made me love math. And then I became really good and got really interested in it and never had issues again. Mind you we are talking about 12 yo me.

I was also a really fast learner. Even as an adult, throughout my professional life, I was always the person learning new things and picking it up super fast.

But then I decided to learn how to code and everything changed.

I understood later that because I never had to study really hard in my life, I didn’t know how exactly I learn when things get complicated.

So, I started, like a lot of people who choose programming as a second career, with online courses and tutorials. And they all go like this:

E-VE-RY freaking course feels like that. They teach you to add 2 + 2 and then ask you to solve a second degree equation using Bhāskara.

Specially starting with Kotlin, where the majority of the courses were tailored for seasoned Java developers. So I kept going, and mostly solving things by brute force, not quite understanding what was happening in the background and hearing from everyone, every time I asked how exactly that was working: “Put it in a black box. You don’t need to understand how this works now”. Which was true for some things but not for others. The problem is, understanding how it works is how I learn.

Then I found a Coursera certificate by Meta on Kotlin that was way better than the average online course, it made a lot of difference, but I was still not able to go as deep as I’d like to go. But It helped. A lot!

Now, a few months into the Launch School course, studying Python, things are different. They are slower and deeper and, because of the mastery-based methodology, you are FORCED to really and deeply understand the concepts you are working with before you move to the next course. There is no shortcut. There is no ‘I think I got this’

So, the other day I was studying about Pass by value vs Pass by reference concepts. And in order to learn that you need to understand, for example, that a variable is a bit more than just an imaginary box that holds something – I also found out those abstract examples people LOVE to use when teaching how to code do not work for me. I need concrete.

“A variable is a identifier attached to a specific value stored in a program’s memory”.

Ok, you create a variable and assign a value to it. The variable will have an address in the computer’s memory and the value (object) will have another address.

You can change the variable in two ways: you can reassign the variable, which will point that variable to a different address in the memory that belongs to a different object. So, if you have:

my_var = "Hello, Rosie"

This is roughly what happens:

What happens if we reassign my_var?

my_var = "The answer is 42"

The variable my_var at the address 76547 is now pointing to a new string object “The answer is 42” that is at address 75643

If you print my_var you would now see The answer is 42.

You can use the id() method to verify the id of the object changes:

my_var = "Hello, Rosie"
print(my_var)
print(id(my_var))
my_var = "The answer is 42"
print(my_var)
print(id(my_var))

my_var simply points to a different object with a different address.

What if we concatenate two strings?

my_var = "Hello, Rosie"
print(my_var)
print(id(my_var))
my_var = "Hello, Rosie. " + "How are you?"
print(my_var)
print(id(my_var))

We get the same behaviour because strings are immutable, so, even tho I kept the first part of the string and added a few more words it still creates a new object.

Now, what if we try that with a list?

my_var = [1, 2, 3, 4]
print(my_var)
print(id(my_var))
my_var[0] = 33
print(my_var)
print(id(my_var))

Because lists are mutable, we can change the elements of the list without actually changing the object. Of course, if you reassign the variable to a completely new list it will create and point to a different object.

my_var = [1, 2, 3, 4]
print(my_var)
print(id(my_var))
my_var = ["a", "b", "c"]
print(my_var)
print(id(my_var))

So, in summary there are two ways of changing a variable: reassigning it and making it point to a different address in memory where a different object “lives” or by mutating the actual object if the object is mutable.

Now, with that out of the way, let’s talk about what happens to functions arguments.

Each language deals with what happend with functions arguments in their own way. The two most common being Pass by Value or Pass by Reference. But what do these terms mean?

Pass By Value

In some language, like C, for example, when you pass an object as argument to a function, the function gets only a copy of the object. So, any actions performed in the object within the function has no effect in the original object.

The original object and its copy are independent of each other and each have their own space and address in memory.

Pass by Reference

In some other languages, what is passed as an argument of a function is actually a reference to the original object. So, in this case both the original variable and the argument of the function point to the same address in the memory, so, what you do to modify the argument within a function will alter the original object as well.

What does Python do?

Both! Something called  pass by object reference.

Yeah! So, in the example below:

def my_function(number):
    number = number + 4
    return number


a = 4
my_function(a) # Returns 8
print(a) # Outputs 4

But, if you are working with a list AND the function is performing a mutating action in the object like the .append() method:

def my_function(my_list):
    my_list.append(4)

a = [1, 2, 3]
print(my_function(a))
print(a) # outputs [1, 2, 3, 4]

However, if, even with a list, if the action is not mutating the object we are back behaving as passing by value:

def my_function(my_list):
    my_list = my_list + [4]

a = [1, 2, 3]
my_function(a)
print(a) # outputs [1, 2, 3]

The thing to keep in mind is: If the object is immutable, it passes by value. If it is mutable and the action is a mutation of the object, it passes by reference.

Now, a little bit from bizarro world. I was playing around with these examples, and when I see my_list = my_list + [4] my mind goes automatically to mmy_list += [4], right?

So I did that, and the output was, surprisingly, [1, 2, 3, 4]

def my_function(my_list):
    my_list += [4]

a = [1, 2, 3]
my_function(a)
print(a) # outputs [1, 2, 3, 4]

I was not expecting that. But, after talking to one of the TAs he, who was also surprised by this behaviour, found out that, in Python, the augmented assignment mutates the object while doing a reassignment. It uses the  __iadd__ method. It takes two arguments: self and other. This method should modify the self object by adding the components of the other object to it, and then return the modified self object. In other words, this method updates the original object without creating a new one.

Thank you, Pete, for finding this out!

SO, be careful when using Python’s augmented assignments if you do not intend to modify the original object.

Discover more from { Code Journey; }

Subscribe now to keep reading and get access to the full archive.

Continue reading