pythonpython-3.xnumpytypesnumpy-ufunc

Use numpy functions on objects extending numbers.Real


PEP 3141 introduces abstract base classes for different kinds of numbers to allow custom implementations. I want to derive a class from numbers.Real and calculate its sine value. Using pythons math-module, this works fine. When I try the same in numpy, I get an error.

from numbers import Real
import numpy as np
import math

class Mynum(Real):
    def __float__(self):
        return 0.0
    # Many other definitions

a = Mynum()

print("math:")
print(math.sin(a))
print("numpy:")
print(np.sin(a))

results in

math:
0.0
numpy:
AttributeError: 'Mynum' object has no attribute 'sin'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
[...]
in <module>
    print(np.sin(a)) TypeError: loop of ufunc does not support argument 0 of type Mynum which has no callable sin method

It seems like numpy tries to call a sin-method of its argument. To me, this is quite confusing since the standard data types (like float) do not have such a method either, but np.sin works on them.

Is there just some kind of hardcoded check for standard data types, and PEP 3141 is not supported? Or did I miss something in my class?

Because it is quite tedious to implement all required methods, here is my current code that works with the math-module:

from numbers import Real
import numpy as np
import math

class Mynum(Real):
    def __init__(self):
        pass

    def __abs__(self):
        pass

    def __add__(self):
        pass

    def __ceil__(self):
        pass

    def __eq__(self):
        pass

    def __float__(self):
        return 0.0

    def __floor__(self):
        pass

    def __floordiv__(self):
        pass

    def __le__(self):
        pass

    def __lt__(self):
        pass

    def __mod__(self):
        pass

    def __mul__(self):
        pass

    def __neg__(self):
        pass

    def __pos__(self):
        pass

    def __pow__(self):
        pass

    def __radd__(self):
        pass

    def __rfloordiv__(self):
        pass

    def __rmod__(self):
        pass

    def __rmul__(self):
        pass

    def __round__(self):
        pass

    def __rpow__(self):
        pass

    def __rtruediv__(self):
        pass

    def __truediv__(self):
        pass

    def __trunc__(self):
        pass

a = Mynum()
print("math:")
print(math.sin(a))
print("numpy:")
print(np.sin(a))

Solution

  • I just answered something like this, but I'll repeat myself

    np.sin(a)
    

    is actually

    np.sin(np.array(a))
    

    What does np.array(a) produce? What's its dtype?

    If it's an object dtype array, that explains that error. With object dtype array, numpy iterates through (the references), and tries to run an appropriate method on each. That's generally ok with operators which can use __add__ like methods, but almost no one defines a sin or exp method.

    From yesterday

    How can I make my class more robust to operator/function overloading?

    Comparing a numeric dtype array with an object dtype:

    In [428]: np.sin(np.array([1,2,3]))
    Out[428]: array([0.84147098, 0.90929743, 0.14112001])
    
    In [429]: np.sin(np.array([1,2,3], object))
    AttributeError: 'int' object has no attribute 'sin'
    
    The above exception was the direct cause of the following exception:
    Traceback (most recent call last):
      File "<ipython-input-429-d6927b9a87c7>", line 1, in <module>
        np.sin(np.array([1,2,3], object))
    TypeError: loop of ufunc does not support argument 0 of type int which has no callable sin method