4

I would like to subclass an immutable type or implement one of my own which behaves like an int does as shown in the following console session:

>>> i=42
>>> id(i)
10021708
>>> i.__iadd__(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__iadd__'
>>> i += 1
>>> i
43
>>> id(i)
10021696

Not surprisingly, int objects have no __iadd__() method, yet applying += to one doesn't result in an error, instead it apparently creates a new int and also somehow magically reassigns it to the name given in the augmented assignment statement.

Is it possible to create a user-defined class or subclass of a built-in immutable one that does this, and if so, how?

martineau
  • 119,623
  • 25
  • 170
  • 301
  • Is there a reason you want `__iadd__` instead of `__add__`? – Michael Mauderer Aug 06 '12 at 22:04
  • 1
    @Michael Mauderer: Yes, I want to know how to emulate the behavior of `int` and other immutable types in this regard. – martineau Aug 06 '12 at 22:14
  • Unless you're doing something hacky and/or unconventional with the operators (which is of course permitted), the statement `i += j` should be equivalent to `i = i + j` for immutable types. Thus one of the following presumably applies if you've implemented `__iadd__` for an immutable type: either you've also implemented `__add__` identically, introducing duplicate code and a maintenance trap, or you've not implemented `__add__` at all, needlessly losing the ability to also use the `+` operator which you could have got for free just by implementing `__add__` instead of `__iadd__`. – Mark Amery Dec 31 '13 at 12:46

4 Answers4

16

Simply don't implement __iadd__, but only __add__:

>>> class X(object):
...     def __add__(self, o):
...             return "added"
>>> x = X()
>>> x += 2
>>> x
'added'

If there's no x.__iadd__, Python simply calculates x += y as x = x + y doc.

phihag
  • 278,196
  • 72
  • 453
  • 469
  • Yes, that does work and do the reassignment. Thanks. So the appears to be no way to differentiate between the two (even though that's likely fine in almost all cases). – martineau Aug 06 '12 at 22:26
  • 4
    This is almost certainly the answer you actually want. Immutable objects should not implement the `__ifoo__` methods. – abarnert Nov 13 '13 at 18:39
3

When it sees i += 1, Python will try to call __iadd__. If that fails, it'll try to call __add__.

In both cases, the result of the call will be bound to the name, i.e. it'll attempt i = i.__iadd__(1) and then i = i.__add__(1).

MRAB
  • 20,356
  • 6
  • 40
  • 33
  • "If that fails" is somewhat imprecise. If the `__iadd__` does exist, it is called, and errors raised by it are reraised. – phihag Aug 06 '12 at 22:07
  • 1
    @phihag To be even more pedantic, I'll just note that `__add__` will also be called if `__iadd__` returns `NotImplemented`. I'm not sure if this is ever useful in practice; perhaps it could be if you wanted to implement an immutable version of a class as a subclass of the mutable version, and override the `__iadd__` method to make `+=` fall back on `__add__`. I suppose you might also use this if you had a class for which in-place modification was reasonable when adding certain types, but not when adding some other types to it, and in the latter case falling back on `__add__` was desirable. – Mark Amery Jan 01 '14 at 20:21
3

The return value of __iadd__() is used. You don't need to return the object that's being added to; you can create a new one and return that instead. In fact, if the object is immutable, you have to.

import os.path

class Path(str):
    def __iadd__(self, other):
        return Path(os.path.join(str(self), str(other)))

path = Path("C:\\")
path += "windows"

print path
kindall
  • 178,883
  • 35
  • 278
  • 309
  • Good answer and example...but @phihag beat you, timing-wise. – martineau Aug 06 '12 at 23:19
  • 1
    Our answers are distinctly different; I'm fine with you choosing his (it's fine) but I just wanted to show that `__iadd__()` will do what you expect. You might want to make regular addition and in-place addition do something totally different, and in that case you'll need to implement both. – kindall Aug 06 '12 at 23:21
  • Ahhh, so `__iadd__()` _can_ return a value -- which is surprising if you think about it -- and you can have both, even on an immutable (sub)class. Subtle but excellent points, guess I'll have to reconsider my choice. – martineau Aug 06 '12 at 23:37
  • +1 Excellent answer. Note that it can be very dangerous and misleading to implement `__iadd__` and `__add__` differently. For example, `path = path + "System"` sets path to `C:\WindowsSystem`. So a real-world implementation of Path should define `__add__` and either implement it or raise an exception. – phihag Aug 07 '12 at 08:41
  • 2
    Yes, you have to be very careful. Note that for some built-in types, however, `__add__` and `__iadd__` are in fact implemented very differently. One example is lists, where `+` produces a new list and `+=` mutates an existing one. If you have multiple references to the list, which one you use can be very important! – kindall Aug 07 '12 at 16:13
  • 2
    This is a bad idea. The language docs [Emulating numeric types](http://docs.python.org/3/reference/datamodel.html#emulating-numeric-types) section explicitly says "These methods should attempt to do the operation in-place (modifying self)". While you _can_ get away with misusing `__iadd__` this way, you shouldn't. Just leave it out, and the interpreter will call `__add__` instead. – abarnert Nov 13 '13 at 18:40
  • -1 for the reason given by abarnert. While this answer is technically correct, it gives the impression of advising implementing `__iadd__` for immutable types (and certainly doesn't warn against doing so), which is pointless, contrary to the documentation, and inconsistent with how Python's built-in immutable types are implemented. – Mark Amery Dec 31 '13 at 12:52
  • 2
    Y'all's definition of "should" is contrary to the dictionary. There is no reason whatsoever not to implement `__iadd__` for immutable types and no reason to "warn" against doing so. It's not as if it won't work perfectly fine. *This is why the return value is used, people.* If `__iadd__` was meant to be used only for mutable types then it wouldn't need the return value. Actually, I find the fact that `+` and `+=` do totally different things with mutable types the confusing thing! Now, a good argument against `__iadd__` is that it's generally redundant if you've implemented `__add__`. – kindall Dec 31 '13 at 14:55
  • Well, my dictionaries say "should" means "must". However the cited docs don't say `__ixxx__` methods shouldn't be implemented for immutable types. What they do say is "These 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_)" (my emboldening). Since in-place operations are, by definition, impossible on immutable types, skipping any attempt to do so and just returning a new value sounds like a perfectly acceptable way of emulating immutable built-ins in respect to these kinds of operations. – martineau Dec 31 '13 at 20:11
  • in py3, there is `pathlib.Path` – AsukaMinato Jun 25 '22 at 19:04
-1
class aug_int:
    def __init__(self, value):
        self.value = value

    def __iadd__(self, other):
        self.value += other
        return self

>>> i = aug_int(34)
>>> i
<__main__.aug_int instance at 0x02368E68>
>>> i.value
34
>>> i += 55
>>> i
<__main__.aug_int instance at 0x02368E68>
>>> i.value
89
>>>
Brenden Brown
  • 3,125
  • 1
  • 14
  • 15