1

I have this python code

x = [1, 2, 3]
y = x
x += [4]
>>> print(y)
[1, 2, 3, 4]

So, this is because x is y is True and if I change x, I change y

But when I do:

x = [1, 2, 3]
y = x
x = x + [4]
>>> print(y)
[1, 2, 3]

and

>>> id(x) == id(y)
False

I wonder what's the difference. I thought x += 1 is shorthand for x = x+1 but obviously there must be a difference.

I was even more confused, when I tried the above to strings:

name = 'John'
name_2 = name
name += ' Doe'

>>> print(name_2)
'John'

So I think the effect of += depends on the object on the left, if it is mutable or not?

Finn
  • 1,999
  • 2
  • 24
  • 29
  • 1
    Yes, your explanation in the last line of your question is correct. (Well, mostly. It doesn't actually depend on whether it is mutable, but whether it defines `__iadd__` to actually mutate the object. But generally mutable types will do that if they allow the operation at all.) – BrenBarn Feb 15 '15 at 18:47
  • See [In-place Operators](https://docs.python.org/3/library/operator.html#inplace-operators). – user2864740 Feb 15 '15 at 18:49
  • @BrenBarn okay, and if I do `x = x + 'foo'` it will always create a new object, because the expression on the right side is a new object? – Finn Feb 15 '15 at 18:52
  • Related: [python: is there a difference between list.extend() and +=?](http://stackoverflow.com/q/6645843) – Martijn Pieters Feb 15 '15 at 18:54

2 Answers2

1

The object "on the left" handles the operator (usually, see the r-operator forms); in this case it is an Inplace Operator.

10.3.2. Inplace Operators

Many operations have an “in-place” version. Listed below are functions providing a more primitive access to in-place operators than the usual syntax does; for example, the statement x += y is equivalent to x = operator.iadd(x, y) ..

The actual result is determined by the "x" object and if it handles __iadd__ (eg. mutated as with lists) or just __add__ (eg. a new result object, as with strings) - the selection of which protocol to use, and what value to return for the assignment, is determined by operator.iadd itself1.

So the shorthand of x += y ~~ x = x + y is only true for some objects - notably those that are immutable and [only] implement __add__.

See How are Python in-place operator functions different than the standard operator functions?


1 Semantically the operator.iadd function works about like:

if x.__iadd__:
    x.__iadd__(y)        # side-effect performed on x,
    return x             # returns original-but-modified object
else
    return x.__add__(y)  # return new object,
                         # __add__ should not have side-effects
Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
0

like @BrenBarn said, if the left hand side object is mutable it will preform an in-place operation. Otherwise a new copy will be returned, and because it was copied their id won't match anymore.

In the background it goes something analogous to this:

>>> import copy
>>> y = [1,2,3]
>>> x = y
>>> x+=[4]
>>> y
[1, 2, 3, 4]
>>> x = copy.copy(y) #perhaps even a deepcopy()? Don't know.
>>> y
[1, 2, 3, 4]
>>> x
[1, 2, 3, 4]
>>> x += [5]
>>> y
[1, 2, 3, 4]
>>> x
[1, 2, 3, 4, 5]

EDIT 1:

class test():
    def __init__(self, ll):
        self.a = ll
    def __add__(self, other):
        return test(ll=self.a+other.a)

>>> a = test([[1,2],[3,4]])
>>> a.a
[[1, 2], [3, 4]]
>>> x = test([[1,2],[3,4]])
>>> x += a
>>> x.a
[[1, 2], [3, 4], [1, 2], [3, 4]]
>>> a.a
[[1, 2], [3, 4]]
>>> x.a[2][0] = 7
>>> x.a
[[1, 2], [3, 4], [7, 2], [3, 4]]
>>> a.a
[[7, 2], [3, 4]]
ljetibo
  • 3,048
  • 19
  • 25
  • 1
    `x = y[:] or x = y.copy()` is all that is needed, you don't need to import copy – Padraic Cunningham Feb 15 '15 at 18:58
  • @PadraicCunningham thnx. Did not actually know that....How about the copy vs deepcopy? I presume that if it's a native type it's jsut copy a class instance you made probably goes by deepcopy? Although I fail to understand how do they keep track of the difference then. – ljetibo Feb 15 '15 at 19:00
  • if you are storing other objects in the list then you would use deepcopy as [:] or list.copy only perform a shallow copy – Padraic Cunningham Feb 15 '15 at 19:01
  • @PadraicCunningham I meant, how does python decide when to do a deepcopy and when a shallow one? I just tested couple of examples and it seems to do a shallow copy only... (seems a bit of a dangerous behavior though?) – ljetibo Feb 15 '15 at 19:08
  • It does not decide, you do. If you have other objects stored in the lists use deepcopy, try creating a list of lists , use `x = y[:] ` then change values in the sublists of x and see what happens. Then use deepcopy and do the same again – Padraic Cunningham Feb 15 '15 at 19:11
  • @PadraicCunningham I'm sorry I'm a bother. But I don't seem to quite understand you. Care to check out the code snippet in my edit? I mean it seems to be a dangerous behaviour to run into accidentally. – ljetibo Feb 15 '15 at 19:15
  • I am talking about when you decide to copy an object not how python handles mutable vs immutable objects. – Padraic Cunningham Feb 15 '15 at 19:25