In this lesson, you’ll learn that Python doesn’t pass objects by value or reference. Python passes objects by assignment instead. In the examples, you’ll see how this differs when the object is mutable or immutable.
Object Passing in Python
00:00 In previous videos, you learned about pass-by-reference and pass-by-value. To review, if you pass an object by value, you pass a copy of its value to the function receiving it.
00:13 This requires the object to be duplicated in memory, and the receiving function will only be able to access this new memory space. When you pass an object by reference, you pass a copy of just the memory address of the object.
00:29 In other words, a pointer. This can be much smaller than the actual object itself. Additionally, the receiving function can now use that memory address to modify the value there directly. At this point, you might be wondering, are function arguments in Python pass-by-value or pass-by-reference?
00:52 The answer is neither. Arguments are what the Python documentation calls pass-by-assignment, meaning that the parameters in the function being called point to the objects in memory that were passed into them as arguments.
It looks like pass-by-reference, but you have to remember that it doesn’t pass the memory address to the value of the object. It passes the address of the entire
PyObject, which itself contains the value.
01:24 Instead of passing an address to the data, we pass an address to data which contains the data.
In Python, we’re passing in a reference to a
PyObject in memory, which behaves differently depending on whether the passed-in object is immutable or mutable. If you pass an immutable type, like the string or
int, it mimics pass-by-value because you can’t actually modify
01:55 the object being passed in, anyway. A new object is always going to be created, which can then be returned from the function. This is why string functions often return the modified string because they can’t mutate the immutable string type directly.
02:13 If you pass a mutable type, it mimics pass-by-reference because you can directly modify the object passed into the function, without having to create a new object.
However, even if you pass a mutable type, like a
list, if you reassign the parameter, instead of modifying its value, a separate object is created for it in memory, and that becomes inaccessible outside the scope of the function.
02:42 I know this is probably confusing, so let me clarify this with a code example.
As usual, I’m here in Visual Studio Code in a new Python script. I’m going to start this by defining a function called
add_10, which will take a parameter and add 10 to it. Inside, I’ll use the
+= operator to
add 10. Outside this function, I will create a new integer name called
y which will point to a
PyObject with a value of 20. Then I will call
add_10, passing in
y, and finally, print out the value of
And when I run this you see that we’re still getting 20. That’s because the
int type is immutable.
When we pass
y to the
add_10 function, what we’re really doing is telling it to create a new name
x, which is the parameter, and bind it to the same object in memory that
y points to. But then, because the type is immutable, incrementing
x by 10 really creates a new object in memory with the value 30, and
x is set to point to it. But
y does not point to it. That’s why printing the value of
y returns 20 still. I’m going to modify the function to instead append 10 to the end of a list that we pass in.
Now I’ll change
y to be a list of integers.
list type is mutable, meaning that this should mimic pass-by-value behavior.
And it does! 10 now appears at the end of the list. The changes we made to
x within the function are reflected in
y, even though it’s outside the scope of the function. No new objects had to be created, so
y both point to this newly modified list.
We do have to be careful with how we use this, though. This means that if we use it in a way that does not call upon its mutability, this still won’t work. To demonstrate, I’m going to remove the call to the
append method, and instead, just reassign
x to a new
list object with random numbers.
I will run this and you see, it appears we’ve lost our pass-by-reference behavior. That’s because inside the function a new object had to be created since we reassigned the
x name completely.
We didn’t modify it using its mutability, we forced Python to change what
x was bound to. It follows that
y no longer point to the same object in memory, and so these changes are not seen outside the scope of this function.
05:44 In the next two videos. I’ll show you some common ways to mimic this pointer, or pass-by-reference, behavior.
When describing pass-by-assignment, it would have been very helpful to show what that means in much the same way as you showed how assigning names to objects works.
Given this function definition and call:
def foo(v): v += 1 w = 1 foo(w) print(w)
The pass-by-assignment behavior makes this roughly equivalent to
w = 1 v = w # pass by assignment -- now v is just another name for w v += 1 # now v is replaced by a new object containing 2 del(v) # after we return from v, the name v no longer exists print(w) # w still points at the same object containing 1
I think the explanation at 1:33 gives the wrong impression that objects are passed differently based on their mutability.
The difference is that objects of mutable types have facilities to change their internal state, while immutable ones don’t.
So the only way to change the value of a name containing an immutable type is by reassignment, at which point that name refers to a different object.
But because, at function call, the arguments get a new name object (a copy) that points to their PyObject, the original name outside the scope is not affected.
To expand on carl’s example:
>>> def foo(v): ... print(id(v)) ... v = v+1 ... print(id(v)) ... >>> w = 1000 >>> id(w) 140247376306096 # w points to PyObject containing 1000 >>> foo(w) 140247376306096 # v points to original PyObject containing 1000 140247376306000 # v now points to different PyObject containing 1001 >>> id(w) 140247376306096 # w is unaffected, still points to original pyObject containing 1000
Hi, first thank you for this awesome tutorial.
In this video I got a bit confused, at 04:19 you said “The list type is mutable, meaning that this should mimic pass-by-value behavior.” But later at 05:10 talking about the list you said “I will run this and you see, it appears we’ve lost our pass-by-reference behavior.” I think since the list is mutable it should mimic the pass-by-reference behavior. Could you please correct me if I’m wrong please?
@raulfz To make things easier, you can think of this as if everything in Python was passed by value. In other words, every argument a function receives is copied inside of it.
The different behavior will depend on the type of the actual parameter. When you pass something of a simple type, such as a string or number, it’ll copy the corresponding value that can be changed independently. On the other hand, passing an object to a function will copy its reference. You’ll have one object but two or more ways to access it.
Become a Member to join the conversation.
carl on July 15, 2020
At ~4:23 in Object Passing in Python you inadvertently described the list type as immutable. It’s fairly clear from the context that you meant to say mutable, but might be worth an edit .