1

Suppose I want to create my own version of an existing class. Let's pick the builtin float class. In this example, I'll create a float like class that do not raise an error when dividing by zero but instead returns the string 'inf' (Remember this is just an example. Avoiding division by zero error is not the main point of this question).

class myfloat(float):
    def __truediv__(self, b):
        if b == 0:
            return 'inf'
        else:
            return myfloat(self.__float__() / b)

It works also when the division is coupled with int or float.

In[88]: a = myfloat(10)
In[89]: a / 2
Out[89]: 5.0
In[90]: a / 0
Out[90]: 'inf'

However, due to the inheritance from the float class, when I carry any other operation (i.e. method) the result will be a float. For example:

In[92]: b = myfloat(20)
In[93]: (a + b) / 0
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-6-f769cd843514> in <module>()
----> 1 (a + b) / 0

ZeroDivisionError: float division by zero

The addition method, still unaltered from the one inherited from the parent float class, obviously returns a float.

So my question: Is there a neat way to make all inherited methods in the child class to return a result which is in the child class, without the need to rewrite all methods? I'm thinking something like a decorator @myfloat being automatically applied to all (applicable) inherited methods to convert their result from the type float to the type myfloat.

Aguy
  • 7,851
  • 5
  • 31
  • 58
  • @Alex no, they just want the result of `(a + b)` to be `myfloat` – juanpa.arrivillaga Feb 28 '18 at 10:25
  • Here, there was a question about class-wrappers/decorators. https://stackoverflow.com/questions/6307761/how-can-i-decorate-all-functions-of-a-class-without-typing-it-over-and-over-for – Alex Feb 28 '18 at 10:35
  • Even with the `__add__` implemented in `b`, using a decorator or not, what would that help if the `a` in `(a + b)` is still a normal float, like in your example? The result would still be a normal float. – Jeronimo Feb 28 '18 at 11:14
  • The best way is to modify the parent class to return a new `self.__class__` object, instead of explicitly using the class name. But since `float` is a built-in, this does not work (and might even be harder because it is possibly implemented in C). – Graipher Feb 28 '18 at 11:27
  • @Jeronimo, interclass operator is a nice bonus but not a must. I'll be happy if when all operands are within the myfloat class the result will be a myfloat as well. – Aguy Feb 28 '18 at 12:14

1 Answers1

1

Here's a way with a decorator which reduces the effort to a single line per method that needs typecasting. It even incorporates @Graipher 's self.__class__ hint, which makes the decorator work without changes in case you some day rename myfloat.

def cast_return(func):
    """ Decorator that casts the return value of a special __method__
        back to the original type of 'self'. """
    def wrapped(self, *args, **kwargs):
        return self.__class__(func(self, *args, **kwargs))
    return wrapped

class myfloat(float):
    def __truediv__(self, b):
        if b == 0:
            return 'inf'
        else:
            return myfloat(self.__float__() / b)

    __mul__ = cast_return(float.__mul__)
    __add__ = cast_return(float.__add__)
    # repeat for all the needed operations

Then you get ...

In [1]: (myfloat(3.3) +  4) / 0
Out[1]: 'inf'

One could even go further and make a list of those functions to typecast them all together in a loop, but I don't think they are that many.

But:

It still doesn't work if another type gets to handle the operation, like here:

In [1]:(4 + myfloat(3.3)) / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-45-6537eb18ab79> in <module>()
     16     __add__ = cast_return(float.__add__)
     17 
---> 18 (4 + myfloat(3.3)) / 0

ZeroDivisionError: float division by zero

I really don't know if there's a way around that.

Jeronimo
  • 2,268
  • 2
  • 13
  • 28