Having issues with when objects are changed and when they aren't in Python. Here is my poorly contrived example below:
class person:
age = 21
class bar:
def __init__(self, arg1):
self.foo = arg1
self.foo.age = 23
def baz(arg1):
arg1.age = 27
def teh(arg1):
arg1 = [3,2,1]
Person1 = person()
bar1 = bar(Person1)
print Person1.age
print bar1.foo.age
baz(Person1)
print Person1.age
print bar1.foo.age
meh = [1,2,3]
teh(meh)
print meh
The output is
23
23
27
27
[1, 2, 3]
So when we declare Person1, Person1.age is 21. A reference to this object is passed to the class constructor of another class instance of bar, called bar1. Any changes made to this reference will change Person1.
This is also the case when we pass Person1 to a normal function, the Person1.age now equals 27.
But why doesn't this work on the variable "meh"? Certainly, if we assign a variable a = meh
and change a = [6, 6, 6]
, then meh will also be changed. I'm confused. Is there any literature on how all this works?
I can see three fundamental Python concepts that can shine some light on the question:
1) First, an assignment from a mutable object like in
self.foo = arg1
is like copying a pointer (and not the value pointed to): self.foo
and arg1
are the same object. That's why the line that follows,
self.foo.age = 23
modifies arg1
(i.e. Person1
). Variables are thus different "names" or "labels" that can point to a unique object (here, a person
object). This explains why baz(Person1)
modifies Person1.age
and bar1.foo.age
to 27, since Person1
and bar1.foo
are just two names for the same person
object (Person1 is bar1.foo
returns True
, in Python).
2) The second important notion is that of assignments. In
def teh(arg1):
arg1 = [3,2,1]
variable arg1
is local, so that the code
meh = [1,2,3]
teh(meh)
first does arg1 = meh
, which means that arg1
is an additional (local) name for list meh
; but doing arg1 = [3, 2, 1]
is like saying "I changed my mind: arg1
will from now on be the name of a new list, [3, 2, 1]". The important thing to keep in mind here is that assignments, despite being denoted with an "equal" sign, are asymmetrical: they give to a (mutable) object on the right-and-side an additional name, given in the left-hand side (that's why you can't do [3, 2, 1] = arg1
, as the left-hand side must be a name [or names]). So, arg1 = meh; arg1 = [3, 2, 1]
cannot change meh
.
3) The last point is related to the question title: "passing by value" and "passing by reference" are not concepts that are relevant in Python. The relevant concepts are instead "mutable object" and "immutable object". Lists are mutable, while numbers are not, which explains what you observe. Also, your Person1
and bar1
objects are mutable (that's why you can change the person's age). You can find more information about these notions in a text tutorial and a video tutorial. Wikipedia also has some (more technical) information. An example illustrates the difference of behavior between mutable and immutable:
x = (4, 2)
y = x # This is equivalent to copying the value (4, 2), because tuples are immutable
y += (1, 2, 3) # This does not change x, because tuples are immutable; this does y = (4, 2) + (1, 2, 3)
x = [4, 2]
y = x # This is equivalent to copying a pointer to the [4, 2] list
y += [1, 2, 3] # This also changes x, because x and y are different names for the same (mutable) object
The last line is not equivalent to y = y + [1, 2, 3]
because this would only put a new list object in variable y
instead of changing the list referred to by both y
and x
.
The three concepts above (variables as names [for mutable objects], asymmetrical assignment, and mutability/immutability) explain many of Python's behaviors.
Certainly, if we assign a variable a = meh and change a = [6, 6, 6], then meh will also be changed.
No, actually, it won't:
>>> meh = [1,2,3]
>>> a = meh
>>> a = [6, 6, 6]
>>> print a
[6, 6, 6]
>>> print meh
[1, 2, 3]
It's the difference between overwriting a variable and modifying the instance pointed to by a variable.
Lists, dictionaries, sets, and objects are mutable types. If you add, remove, set, get, or otherwise modify something in an instance of them, it updates everything that references that instance.
If, however, you assign a completely new instance of the type to a variable, that changes the reference stored in that variable, and thus the old referenced instance is not changed.
a = [1,2,3] # New instance
a[1] = 4 # Modifying existing instance
b = {'x':1, 'y':2} # New instance
b['x'] = 3 # Modifying existing instance
self.x = [1,2,3] # Modifying existing object instance pointed to by 'self',
# and creating new instance of a list to store in 'self.x'
self.x[0] = 5 # Modifying existing list instance pointed to by 'self.x'
Python does not have pass-by-value, nor pass-by-reference, but instead uses pass-by-object -- in other words, objects are passed directly into functions, and bound to the parameter name given in the function definition.
When you do spam = "green", you have bound the name spam to the string object "green"; if you then do eggs = spam you have not copied anything, you have not made reference pointers; you have simply bound another name, eggs, to the same object ("green" in this case). If you then bind spam to something else (spam = 3.14159) eggs will still be bound to "green".
In your teh
function you are not changing/modifying/mutating the passed in object, you are reassigning the name arg1
to a different list. To change arg1
you want this:
def teh(arg1):
arg1[:] = [3, 2, 1]