¿Por qué los métodos de clases que heredan de matrices numpy devuelven cosas diferentes?

Veamos algunos comportamientos muy simples de matrices numpy:

import numpy as np arr = np.array([1,2,3,4,5]) max_value = arr.max() # returns 5 

Ahora creo una clase que hereda de np.ndarray :

 class CustomArray(np.ndarray): def __new__(cls, *args, **kwargs): obj = np.array(*args, **kwargs).view(cls) return obj new_arr = CustomArray([1,2,3,4,5]) 

En realidad, no he cambiado ningún comportamiento; acabo de hacer un cambio nominal en la clase del objeto.

Y todavía:

 new_max_value = new_arr.max() # returns CustomArray(5) 

El valor de retorno es una instancia de CustomArray ? ¿Por qué? arr.max() no devolvió una instancia np.ndarray , solo un número entero liso.

De manera similar, ¿por qué tanto arr == new_arr y new_arr == arr devuelven instancias de CustomArray ? ¿No debería la llamada anterior arr.__eq__(new_arr) , que debería devolver una instancia np.ndarray ?

EDITAR:

Tenga en cuenta que __new__ método __new__ por el bien de los constructores fáciles. Por ejemplo, el equivalente de np.array([1,2,3,4,5]) puede ser simplemente CustomArray([1,2,3,4,5]) , mientras que si heredara claramente de np.ndarray , habría tengo que hacer algo como new_arr = CustomArray((5,)) ; new_arr[:] = np.array([1,2,3,4,5]) .

Siguiendo los documentos numpy: se llama a array_wrap al final de numpy ufuncs y otras funciones numpy, para permitir que una subclase establezca el tipo de valor de retorno y actualice los atributos y metadatos.

 class CustomArray(np.ndarray): def __new__(cls, a, dtype=None, order=None): obj = np.asarray(a, dtype, order).view(cls) return obj def __array_wrap__(self, out_arr, context=None): return np.ndarray.__array_wrap__(self, out_arr, context) c = CustomArray([1,2,3,4]) c.max() # returns 4