vitiral
vitiral

Reputation: 9220

Python General Wrapper class for any data

I want to write a general wrapper class for any data. Primarily I am trying to make a function that will automatically store data in the hard drive (kind of an auto-swap) if it hasn't been used in a while.

I have been programming with python for years now, but I just can't find a "pythonic" way of doing this. I eventually resorted to a script that would write every possible method for me -- but even that ended up not working. Check out the below.

I can't even get this simple code to work -- why???

class adder(object):
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        print 'adding'
        return self.value.__add__(other)

    def __radd__(self, other):
        print 'radding'
        return self.value.__radd__(other)

a = adder(10)
b = adder(12)
print a + b 

OUTPUT

adding
Traceback (most recent call last):
  File "/home/user/Projects/CloudformDesign/PythonCloudform/cloudtb/playground/adding_classes.py", line 42, in <module>
    print a + b 
TypeError: unsupported operand type(s) for +: 'adder' and 'adder'

I am of course aware that I could not use the add and radd functions (just use the + syntax). I did this to see what in the world was happening: here is the results

class adder2(object):
    def __init__(self, value):
        self.value = value

    def __add__(self, other):
        print 'a2'
        return self.value + other

    def __radd__(self, other):
        print 'ra2'
        return other + self.value

a = adder(adder2(10))
b = adder(adder2(12))

print a + b

Output:

adding
a2
radding
ra2
22

So clearly it is going into the "adding" function, then going into the second classe's adding function, then going into the first class's "radding" function, and then finally going into the second classes "radding" function -- what in the WORLD is happening??? Furthermore, THIS works when the other didn't -- I am so confused.

Also, if you know of any code that has done this, please let me know!

Upvotes: 1

Views: 1546

Answers (2)

Thomas
Thomas

Reputation: 6752

Emulating numeric types in Python (docs)

When the expression a + b is evaluated, first a is checked an __add__ implementation; if it has one, a.__add__(b) is executed. If it doesn't, or running a.__add__(b) returns the special value NotImplemented, then b is checked for an __radd__ implementation; if it doesn't have one or it returns NotImplemented, you can't add them.

What's going on in your code

A reminder of our objects:

>>> a = adder(adder2(10))
>>> a.value
adder2
>>> a.value.value
10

>>> b = adder(adder2(12))
>>> b.value
adder2
b.value.value
12

print a + b could be described as:

a.__add__(b)                    which adder.__add__ implements as
a.value.__add__(b)              which adder2.__add__ implements as
a.value.value + b               so first we try
a.value.value.__add__(b)        but int.__add__ fails (returns NotImplemented) so we try
b.__radd__(a.value.value)       which adder.__radd__ implemented as
b.value.__radd__(a.value.value) which adder2.__radd__ implements as
a.value.value + b.value.value   which int.__add__ implements as addition

What to do about it

I think you're trying to create a type which can be added to numbers, or other objects of its type. If so, you want

class Adder(object):
    def __init__(self, value):
        self.value
    def __add__(self, other):
        return self.value + other
    def __radd__(self, other):
        return self.value + other

which is equivalent to the version without +:

class Adder(object):
    def __init__(self, value):
        self.value
    def __add__(self, other):
        r = self.value.__add__(other)
        if r is NotImplemented:
            r = other.__radd__(self.value) # so if other is a adder, use its __radd__ 
        return r                           # since our value's __add__ will return
    __radd__ = __add__

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251383

When you do a + b, it calls call self.value.__add__(other). Since a.value is 10, this calls (10).__add__(b). Since b is an adder instance, the integer 10 does not know how to add itself to b, so it returns NotImplemented. The __radd__ method of b is not called at this stage! Since you return the result of self.value.__add__(other), your __add__ method also returns NotImplemented. Once this happens, b.__radd__(a) is called, and also returns NotImplemented for the same reason --- it delegates to (12).__radd__(a), which fails. Since both return NotImplemented, Python thinks the objects don't know how to add to each other, so it raises the error.

The solution is, as you found, to use + instead of directly calling the magic methods. (You don't need to make a separate class for that; just change your adder class to call self.value + other instead of self.value__add__(other), etc.) When you use +, you activate Python's internal machinery, whereby it calls __add__ on the left operand and then, if that doesn't work, calls __radd__ on the right operand. When you directly call __add__ and __radd__, you are bypassing this internal machinery and only calling one of the two. This means that you never get to a state where both objects "reduce" themselves to their underlying values. Instead, you try the left-hand __add__, and when it fails, you don't "fall back" to __radd__ --- you start again from the beginning with __radd__, losing the "value reduction" you did in the first part.

The crucial thing is that 10 + b knows to call b.__radd__(10) if (10).__add__(b) fails, but if you just directly call (10).__add__(b), it doesn't know anything about __radd__, so b.__radd__(10) is never called.

Upvotes: 3

Related Questions