4
In [38]: d = set(range(3))

In [39]: d
Out[39]: set([0, 1, 2])

In [40]: for i in d:
    d  -= set([2])
   ....:     
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
/home/gridlex/workspace/HomeBackSites/nava_scripts/<ipython-input-40-b79926ab34ec> in <module>()
----> 1 for i in d:
      2     d  -= set([2])
      3 

RuntimeError: Set changed size during iteration

what is the difference between these two assignments in python?

1.d -= set([2])

2 d = d - set([2])

In [41]: d = set(range(3))

In [42]: for i in d:
    d = d - set([2])
   ....:     

In [43]: d
Out[43]: set([0, 1])
Nava
  • 6,276
  • 6
  • 44
  • 68
  • 1
    Damn you search and operators. Surely this is a duplicate. – Martijn Pieters Nov 13 '13 at 18:34
  • Was thinking the same thing. – Captain Skyhawk Nov 13 '13 at 18:34
  • Searching for `__iadd__`, `__isub__`, etc. should be easier. But the first thing I found, [here](http://stackoverflow.com/questions/11836570/how-to-implement-iadd-for-immutable-type), is about how to implement +=, not what it does, and its accepted answer is misleading. – abarnert Nov 13 '13 at 18:42

1 Answers1

3

With immutable types like integers, a -= b is the same thing as a = a - b: It creates a new value, a - b, and re-binds the name a to refer to that new value instead of the old one.

But with mutable types like sets, a -= b changes the value that a is pointing to in-place. (It also re-binds a to the same value it's already referring to, but that's not important.)

The best way to see this is by looking at the object's identity:

>>> s1 = set(range(3))
>>> s2 = s1
>>> s1, id(s1), s2, id(s2)
({0, 1, 2}, 4303749432, {0, 1, 2}, 4303749432)
>>> s1 -= {1}
>>> s1, id(s1), s2, id(s2)
({0, 2}, 4303749432, {0, 2}, 4303749432)
>>> s1 = s1 - {2}
>>> s1, id(s1), s2, id(s2)
({0}, 4303749664, {0, 2}, 4303749432)

Notice that the -= leaves s1 still referring to the same set as s2, and changes that set; the - leaves s1 referring to a brand-new set with a different id, and doesn't affect s2.


Under the covers, a = a - b is roughly* equivalent to a = a.__sub__(b), while a -= b is equivalent to a = a.__isub__(b). Except that if there is no __isub__ method, a -= b just uses __sub__ instead.

The fact that __isub__ changes the value, while __sub__ returns a new value, isn't really enforced by the language, but it's something that's true of all built-in and stdlib types, and expected to be true of any custom types. It's described in Emulating numeric types in the docs:

These [__ifoo__] methods should attempt to do the operation in-place (modifying self) and return the result (which could be, but does not have to be, self). If a specific method is not defined, the augmented assignment falls back to the normal methods. For instance, to execute the statement x += y, where x is an instance of a class that has an __iadd__() method, x.__iadd__(y) is called. If x is an instance of a class that does not define a __iadd__() method, x.__add__(y) and y.__radd__(x) are considered, as with the evaluation of x + y.


* It's not exactly equivalent because of (a) __rsub__, (b) types implemented in C (like set), and (c) rules for looking up certain special methods being different from normal methods.

abarnert
  • 354,177
  • 51
  • 601
  • 671