1

Using multiplication operator mul for example, code

import numpy as np

v = 1.0

a = float(v)
b = np.float64(v)

print(type(a*b), type(a.__mul__(b)))

a = complex(v)
b = np.complex128(v)
print(type(a*b), type(a.__mul__(b)))

produce the following output

<class 'numpy.float64'> <class 'float'>
<class 'numpy.complex128'> <class 'complex'>

Since <class 'float'>.__mul__(<class 'numpy.float64'>) and <class 'complex'>.__mul__(<class 'numpy.complex128'>) do not in fact return NotImplemented, according to Python documentation results should be <class 'float'> and <class 'complex'>, respectively.

Why is it not so? It seems binary arithmetic operators (+,-,*,/,etc) for builtin numeric types behave differently than their corresponding __op__() functions?

8xb2vsda62
  • 25
  • 5
  • check `b.__rmul__(a)` – hpaulj Jul 08 '21 at 13:47
  • `b.__rmul__(a)` is correct in both cases. The question is why `a.__mul__(b)` is ignored when it **does not** return `NotImplemented`. – 8xb2vsda62 Jul 08 '21 at 13:52
  • `numpy` objects have a higher priority., and control the form of the '*' – hpaulj Jul 08 '21 at 14:24
  • what is this priority? I can't seem to find anything about this in Python documentation. – 8xb2vsda62 Jul 09 '21 at 07:03
  • I don't recall where I read about this priority, probably in some other SO. Here's a some what related question, dealing with the distinction between a 0d array and numpy 'scalar', https://stackoverflow.com/questions/66169279/what-is-a-numpy-array-of-size-why-does-multiplying-it-by-1-return-a-numpy – hpaulj Jul 09 '21 at 16:10

1 Answers1

1

I may be wrong about the priority. There is a priority value that controls how ndarray and its subclasses interact, but with the Python numbers it may be just a matter of NotImplemented, or subclassing

https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types

Note If the right operand’s type is a subclass of the left operand’s type 
and that subclass provides a different implementation of the reflected 
method for the operation, this method will be called before the left 
operand’s non-reflected method. This behavior allows subclasses to 
override their ancestors’ operations.

With your example:

In [107]: a = 1.0; b = np.float64(1.0)
In [108]: type(a*b)
Out[108]: numpy.float64
In [109]: type(a.__mul__(b))
Out[109]: float
In [110]: type(b.__rmul__(a))
Out[110]: numpy.float64
In [111]: type(b).__mro__
Out[111]: 
(numpy.float64,
 numpy.floating,
 numpy.inexact,
 numpy.number,
 numpy.generic,
 float,
 object)

b controls a*b because type(b) is a subclass of type(a). b.__rmul__ is used.

np.complex128 is also a subclass of complex.

With integers, not implemented is the factor

In [113]: c = 1; d = np.int64(1)
In [114]: type(c*d)
Out[114]: numpy.int64
In [115]: c.__mul__(d)
Out[115]: NotImplemented
In [116]: d.__rmul__(c)
Out[116]: 1
In [117]: type(_)
Out[117]: numpy.int64
In [118]: type(d).__mro__
Out[118]: 
(numpy.int64,
 numpy.signedinteger,
 numpy.integer,
 numpy.number,
 numpy.generic,
 object)

np.int64 is not a subclass of int.

hpaulj
  • 221,503
  • 14
  • 230
  • 353