3

When I subclass some class, say int, and customise it's __add__ method and call super().__add__(other) it returns an instance of int, not my subclass. I could fix this by adding type(self) before every super() call in every method that returns an int, but that seems excessive. There must be a better way to do this. The same thing happens with floats and fractions.Fractions.

class A(int):
    def __add__(self, other):
        return super().__add__(other)

x = A()
print(type(x + 1))

Output:
<class 'int'>

Expected Output:
<class '__main__.A'>

RedKnite
  • 1,525
  • 13
  • 26
  • 3
    This answer precisely talks about this: https://stackoverflow.com/a/46196226/7915759 – Todd Apr 21 '20 at 02:22

3 Answers3

2

This can be done using a descriptor. The following class uses special methods that have a special effect when that class is instantiated inside class body.

class SuperCaller:

    def __set_name__(self, owner, name):
        """Called when the class is defined. owner is the class that's being
        defined. name is the name of the method that's being defined.
        """
        method = getattr(super(owner, owner), name)
        def call(self, other):
            # Note that this self shadows the __set_name__ self. They are two
            # different things.
            return type(self)(method(self, other))
        self._call = call

    def __get__(self, instance, owner):
        """instance is an instance of owner."""
        return lambda other: self._call(instance, other)


class A(int):
    __add__ = SuperCaller()


x = A()
print(type(x + 1))

Output: <class '__main__.A'>

luther
  • 5,195
  • 1
  • 14
  • 24
  • It took some trial and error to get this code to work. I don't know why the `super` call requires two arguments instead of one. [The doc](https://docs.python.org/3/library/functions.html#super) doesn't seem to explain it. – luther Apr 21 '20 at 03:31
  • 1
    Basically, the unbound `super(A)` doesn't give you access to the MRO of `A`. Instead, you can bind that object to an object or type, then that bound object will give you access to the MRO of the object or type starting after `A`. There's an [open issue](https://bugs.python.org/issue37808) to deprecate the single argument `super`, because there really isn't a use for it in modern Python. – Patrick Haugh Apr 21 '20 at 03:54
  • Thanks for the explanation. It's weird that it can't get the MRO from the first argument. By "bind that object", do you mean assigning to its `__class__` attribute? – luther Apr 21 '20 at 07:04
  • 1
    You use `__get__`: https://stackoverflow.com/questions/30190185/how-can-i-use-super-with-one-argument-in-python – Patrick Haugh Apr 21 '20 at 13:31
1

One approach would be to create a decorator that could wrap the desired math operations with the cast:

def wrap_math(c):
    def wrapped(orig):
        return lambda s, o: c(orig(s,o))

    maths = ["__add__", "__sub__"]
    for op in maths:
        func = wrapped(getattr(c, op))
        setattr(c, op, func)

return c

@wrap_math
class Special(int)
    pass

 x = Special(10)
 type(x + 10)

Complete the list of functions you want to wrap and you should be good to go.

mageos
  • 1,216
  • 7
  • 15
1

the super() function calls the method from the parent class, which is int in this case. Instead you should initialize the class within the __add__ method:

class A(int):
    def __add__(self, number):
        return A(self.numerator + number)

x = A(4)
print(type(x + 1))
Lord Elrond
  • 13,430
  • 7
  • 40
  • 80