Trouble understanding passing values and reference

2019-02-25 08:49发布

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?

3条回答
等我变得足够好
2楼-- · 2019-02-25 09:25

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.

查看更多
成全新的幸福
3楼-- · 2019-02-25 09:43

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'
查看更多
一纸荒年 Trace。
4楼-- · 2019-02-25 09:48

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]
查看更多
登录 后发表回答