pythoncontainerssequencemagic-methodsaugmented-assignment

Magic method for `self[key] += value`?


There is a Python magic method __setitem__ for assignment = to sequence types. Are there magic methods for augmented assignment += at the container level? The operation does appear to break down gracefully to an augmented assignment of the item-type. That's logical. But there's a magic method for almost everything else, I thought there should be a set of methods for these operators on a sequence type. Did I miss it?

The application is a kind of database. Think of implementing __getitem__ with a SELECT and __setitem__ with INSERT. There should be something like __additem__ etc. for UPDATE, right?

from __future__ import print_function
class Item(object):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return "Item " + str(self.value)
    def __iadd__(self, other):
        print("Item iadd", self, other)
        return Item(self.value + "+" + other.value)

class Container(object):
    def __getitem__(self, key):
        print("Container getitem", key)
        return Item(key)
    def __setitem__(self, key, value):
        print("Container setitem", key, value)
        return self
    def __additem__(self, key, value):
        print("Container additem would be nice", key, value)
        return self

Here's the SELECT:

>>> c = Container()
>>> _ = c['key']
Container getitem key

Here's the INSERT:

>>> c['key'] = Item('other')
Container setitem key Item other

How to implement UPDATE? Without breaking down into SELECT and INSERT:

>>> c['key'] += Item('other')
Container getitem key
Item iadd Item key Item other
Container setitem key Item key+other

So is there a way to implement augmented assignment differently than the broken-down steps? Without putting all the behavior in the Item class?


You can cut and paste the above code into python interpreter, or try it on this Python fiddle: http://pythonfiddle.com/add-item-for-sequence/


A related question shows the decompilation of self[key] += value


Solution

  • No, there's no magic __additem__ method.

    c[k] += v gets turned into the magic method calls: c.__setitem__(c.__getitem__(k).__iadd__(v)).

    The __setitem__ call is included because not all Python objects support in-place addition. Immutable objects (like int and float) do not, and so __add__ will be called instead of __iadd__ (and __add__ must return a new value).

    I think the only way you could make your database work the way you want is writing __getitem__ such that it returns a special "item proxy" type. The proxy would do nothing initially (it has no behavior of its own). However, when an attribute or method was looked up on it (or a __magic__ method was called), it would do the necessary database operations to make it work. For most methods and attribute lookups it would need to query the database to fetch the actual data, and then return an appropriate method or value. If it was a known in-place modification method that was called (like append or __iadd__, it could do a different database operation instead (like an UPDATE instead of a SELECT).

    Writing such a proxy type would not be very easy, but not absurdly hard either. There would just be a lot of corner cases to cover if you wanted to handle many types of objects. Depending on how narrow a set of types your database can hold, it might be possible to narrow the list of methods and attributes you need the proxy to replicate to just a few (e.g. the arithmetic and comparison operators, and maybe __int__ and __float__ if you only need to support numeric types).

    On the other hand, you may find that it's not worth the development effort when you can just do a SELECT and an INSERT with less code.