views:

101

answers:

3
class TrafficData(object):
    def __init__(self):
        self.__data = {}
    def __getitem__(self, epoch):
        if not isinstance(epoch, int):
            raise TypeError()
        return self.__data.setdefault(epoch, ProcessTraffic())
    def __iadd__(self, other):
        for epoch, traffic in other.iteritems():

            # these work
            #existing = self[epoch]
            #existing += traffic

            # this does not
            self[epoch] += traffic # here the exception is thrown

        return self

In the above trimmed down code, I do not expect an item assignment, yet apparently one is occurring on the marked line, and throwing the following exception:

  File "nethogs2.py", line 130, in __iadd__
    self[epoch] += traffic
TypeError: 'TrafficData' object does not support item assignment

However if I instead use the preceding 2 commented out lines, no exception is thrown.

As I see it, the 2 should behave in the same way. self[epoch] returns a reference to an object, and it's modified in place through that objects __iadd__. What am I misunderstanding here? I frequently run into this problem when using dictionaries.

Update0

It's probably worth pointing out that the values in self.__data have __iadd__ defined, but not __add__, and I'd much prefer to modify the value in place if possible. I would also like to avoid creating a __setitem__ method.

Update1

Below is a test case demonstrating the problem, I've left the code above for existing answers.

class Value(object):
    def __init__(self, initial=0):
        self.a = initial
    def __iadd__(self, other):
        self.a += other
        return self
    def __str__(self):
        return str(self.a)

class Blah(object):
    def __init__(self):
        self.__data = {}
    def __getitem__(self, key):
        return self.__data.setdefault(key, Value())

a = Blah()
b = a[1]
b += 1
print a[1]
a[1] += 2
print a[1]
+6  A: 

What you are exactly doing in:

self[epoch] += traffic

is:

self[epoch] = self[epoch] + traffic

But you haven't defined __setitem__ method, so you can do that on self.

You also need:

def __setitem__(self, epoch, value):
        self.__data[epoch] = value

or something similar.

gruszczy
can you link to some documentation on this syntactical fact?
Matt Joiner
Here operators are described. Don't know exactly where to find it in official docs: http://www.tutorialspoint.com/python/python_basic_operators.htm
gruszczy
@Matt: http://docs.python.org/reference/datamodel.html#object.__setitem__
SilentGhost
@SilentGhost, no I meant the `a += b -> a = a + b` thing.
Matt Joiner
This: http://docs.python.org/reference/datamodel.html#emulating-numeric-types. "If x is an instance of a class that does not define a __iadd__() method..."
S.Lott
@Matt: See also Augmented Assignment Statements, http://docs.python.org/reference/simple_stmts.html#augmented-assignment-statements
Will McCutchen
@S.Lott, but it _does_ define `__iadd__`
Matt Joiner
@Will, the link appears to be on the right topic, but doesn't explain why my in-place add (`__iadd__`) method is not being invoked...
Matt Joiner
@Matt: `self[epoch]` is what type? Does that type define `__iadd__`?
S.Lott
@S.Lott: yes it does. I've distilled out the situation in Update1 in my question.
Matt Joiner
A: 

The code:

self[epoch] += traffic

Is syntactic sugar for:

self[epoch] = self[epoch] + traffic

So the assignment is not unexpected it is the assignment in +=. So you also need to override the __setitem__() method.

Tendayi Mawushe
+1  A: 

It's probably worth pointing out that the values in self.__data have __iadd__ defined, but not __add__, and I'd much prefer to modify the value in place if possible.

To add some precision to previous answers, under the circumstances you describe, self[epoch] += traffic translates exactly to:

self[epoch] = self[epoch].__iadd__(traffic)

So if all you want are the side effects of __iadd__, without the item-assignment part, your choices are limited to the workaround that you've already identified in the comments in the code you've posted, or calling __iadd__ yourself -- possibly through the operator module, though I believe operator.__iadd__(self[epoch], traffic) has no added value compared to the simpler self[epoch].__iadd__(traffic) (when self[epoch] does have a __iadd__ method).

Alex Martelli
Do you happen to know why `self[epoch] += traffic` isn't translated into `self[epoch].__iadd__(traffic)`? Other than using the "commented code" I provide, the best way I can find to keep my syntax is to provide a `__setitem__` that performs `assert self[key] is value`... or nothing at all (since the extracted reference as you show above will be modified in place)
Matt Joiner
@Matt, the _definition_ of `+=` is to perform an assignment on the left-hand operand (of either `__add__`'s or `__iadd__`'s results) -- it's just more regular that way than if it might either have the assignment or not (it matters a lot, e.g., in a `shelve` instance!). Yes, if you _do_ want to write a `__setitem__`, it can be a noop or just a check (but you did mention you'd rather _not_ write it;-).
Alex Martelli