The Python documentation seems unclear about whether parameters are passed by reference or value, and the following code produces the unchanged value ‘Original’
class PassByReference: def __init__(self): self.variable = 'Original' self.change(self.variable) print(self.variable) def change(self, var): var = 'Changed'
Is there something I can do to pass the variable by actual reference?
How to pass a variable by reference in Python? Answer #1:
Arguments are passed by assignment. The rationale behind this is twofold:
- the parameter passed in is actually a reference to an object (but the reference is passed by value)
- some data types are mutable, but others aren’t
- If you pass a mutable object into a method, the method gets a reference to that same object and you can mutate it to your heart’s delight, but if you rebind the reference in the method, the outer scope will know nothing about it, and after you’re done, the outer reference will still point at the original object.
- If you pass an immutable object to a method, you still can’t rebind the outer reference, and you can’t even mutate the object.
To make it even more clear, let’s have some examples.
List – a mutable type
Let’s try to modify the list that was passed to a method:
def try_to_change_list_contents(the_list): print('got', the_list) the_list.append('four') print('changed to', the_list) outer_list = ['one', 'two', 'three'] print('before, outer_list =', outer_list) try_to_change_list_contents(outer_list) print('after, outer_list =', outer_list)
before, outer_list = ['one', 'two', 'three'] got ['one', 'two', 'three'] changed to ['one', 'two', 'three', 'four'] after, outer_list = ['one', 'two', 'three', 'four']
Since the parameter passed in is a reference to
outer_list, not a copy of it, we can use the mutating list methods to change it and have the changes reflected in the outer scope.
Now let’s see what happens when we try to change the reference that was passed in as a parameter:
def try_to_change_list_reference(the_list): print('got', the_list) the_list = ['and', 'we', 'can', 'not', 'lie'] print('set to', the_list) outer_list = ['we', 'like', 'proper', 'English'] print('before, outer_list =', outer_list) try_to_change_list_reference(outer_list) print('after, outer_list =', outer_list)
before, outer_list = ['we', 'like', 'proper', 'English'] got ['we', 'like', 'proper', 'English'] set to ['and', 'we', 'can', 'not', 'lie'] after, outer_list = ['we', 'like', 'proper', 'English']
the_list parameter was passed by value, assigning a new list to it had no effect that the code outside the method could see. The
the_list was a copy of the
outer_list reference, and we had
the_list point to a new list, but there was no way to change where
String – an immutable type
It’s immutable, so there’s nothing we can do to change the contents of the string
Now, let’s try to change the reference
def try_to_change_string_reference(the_string): print('got', the_string) the_string = 'In a kingdom by the sea' print('set to', the_string) outer_string = 'It was many and many a year ago' print('before, outer_string =', outer_string) try_to_change_string_reference(outer_string) print('after, outer_string =', outer_string)
before, outer_string = It was many and many a year ago got It was many and many a year ago set to In a kingdom by the sea after, outer_string = It was many and many a year ago
Again, since the
the_string parameter was passed by value, assigning a new string to it had no effect that the code outside the method could see. The
the_string was a copy of the
outer_string reference, and we had
the_string point to a new string, but there was no way to change where
I hope this clears things up a little.
EDIT: It’s been noted that this doesn’t answer the question that @David originally asked, “Is there something I can do to pass the variable by actual reference?”. Let’s work on that.
How do we get around this?
As @Andrea’s answer shows, you could return the new value. This doesn’t change the way things are passed in, but does let you get the information you want back out:
def return_a_whole_new_string(the_string): new_string = something_to_do_with_the_old_string(the_string) return new_string # then you could call it like my_string = return_a_whole_new_string(my_string)
If you really wanted to avoid using a return value, you could create a class to hold your value and pass it into the function or use an existing class, like a list:
def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change): new_string = something_to_do_with_the_old_string(stuff_to_change) stuff_to_change = new_string # then you could call it like wrapper = [my_string] use_a_wrapper_to_simulate_pass_by_reference(wrapper) do_something_with(wrapper)
Although this seems a little cumbersome.
The problem comes from a misunderstanding of what variables are in Python. If you’re used to most traditional languages, you have a mental model of what happens in the following sequence:
a = 1 a = 2
You believe that
a is a memory location that stores the value
1, then is updated to store the value
2. That’s not how things work in Python. Rather,
a starts as a reference to an object with the value
1, then gets reassigned as a reference to an object with the value
2. Those two objects may continue to coexist even though
a doesn’t refer to the first one anymore; in fact they may be shared by any number of other references within the program.
When you call a function with a parameter, a new reference is created that refers to the object passed in. This is separate from the reference that was used in the function call, so there’s no way to update that reference and make it refer to a new object. In your example:
def __init__(self): self.variable = 'Original' self.Change(self.variable) def Change(self, var): var = 'Changed'
self.variable is a reference to the string object
'Original'. When you call
Change you create a second reference
var to the object. Inside the function you reassign the reference
var to a different string object
'Changed', but the reference
self.variable is separate and does not change.
The only way around this is to pass a mutable object. Because both references refer to the same object, any changes to the object are reflected in both places.
def __init__(self): self.variable = ['Original'] self.Change(self.variable) def Change(self, var): var = 'Changed'
I found the other answers rather long and complicated, so I created this simple diagram to explain the way Python treats variables and parameters.
It is neither pass-by-value or pass-by-reference – it is call-by-object. See this, by Fredrik Lundh:
Here is a significant quote:
“…variables [names] are not objects; they cannot be denoted by other variables or referred to by objects.”
In your example, when the
Change method is called–a namespace is created for it; and
var becomes a name, within that namespace, for the string object
'Original'. That object then has a name in two namespaces. Next,
var = 'Changed' binds
var to a new string object, and thus the method’s namespace forgets about
'Original'. Finally, that namespace is forgotten, and the string
'Changed' along with it.
Think of stuff being passed by assignment instead of by reference/by value. That way, it is always clear, what is happening as long as you understand what happens during the normal assignment.
So, when passing a list to a function/method, the list is assigned to the parameter name. Appending to the list will result in the list being modified. Reassigning the list inside the function will not change the original list, since:
a = [1, 2, 3] b = a b.append(4) b = ['a', 'b'] print a, b # prints [1, 2, 3, 4] ['a', 'b']
Since immutable types cannot be modified, they seem like being passed by value – passing an int into a function means assigning the int to the function’s parameter. You can only ever reassign that, but it won’t change the original variables value.
There are no variables in Python
The key to understanding parameter passing is to stop thinking about “variables”. There are names and objects in Python and together they appear like variables, but it is useful to always distinguish the three.
- Python has names and objects.
- Assignment binds a name to an object.
- Passing an argument into a function also binds a name (the parameter name of the function) to an object.
That is all there is to it. Mutability is irrelevant to this question.
a = 1
This binds the name
a to an object of type integer that holds the value 1.
b = x
This binds the name
b to the same object that the name
x is currently bound to. Afterward, the name
b has nothing to do with the name
How to read the example in the question
In the code shown in the question, the statement
self.Change(self.variable) binds the name
var (in the scope of function
Change) to the object that holds the value
'Original' and the assignment
var = 'Changed' (in the body of function
Change) assigns that same name again: to some other object (that happens to hold a string as well but could have been something else entirely).
How to pass by reference
So if the thing you want to change is a mutable object, there is no problem, as everything is effectively passed by reference.
If it is an immutable object (e.g. a bool, number, string), the way to go is to wrap it in a mutable object.
The quick-and-dirty solution for this is a one-element list (instead of
[self.variable] and in the function modify
The more pythonic approach would be to introduce a trivial, one-attribute class. The function receives an instance of the class and manipulates the attribute.
Hope you learned something from this post.
Follow Programming Articles for more!