Skip to content

ufuncs return 0d arrays instead of scalars for ndarray subclasses #5819

Closed
@whoburg

Description

@whoburg

I'm running into a few unexpected behaviors when subclassing ndarray, as described here: (http://docs.scipy.org/doc/numpy/user/basics.subclassing.html).

Here's a simple subclass that doesn't try to add any functionality:

import numpy as np

class MyArray(np.ndarray):

    def __new__(cls, input_array, info=None):
        # cast to be our class type
        obj = np.asarray(input_array).view(cls)
        return obj

    def __array_finalize__(self, obj):
        pass

Notice that the behavior of .sum() differs between an ndarray and MyArray:

>>> np.array([1,2,3]).sum()
6
>>> MyArray([1,2,3]).sum()
MyArray(6)

I'd like my subclass to have the np.array behavior -- I don't want MyArray.sum() to return 0-dimensional arrays.

Before I try to solve this problem, I want to point out that the difference is also there for the object dtype. Here's a pretty useless object definition, that supports addition:

class SelfishObject(object):
    def __add__(self, other):
        return self
    def __radd__(self, other):
        return self + other

Notice that the behavior of .sum() still differs in terms of return type:

>>> np.array([SelfishObject(), SelfishObject()]).sum()
<__main__.SelfishObject object at 0x1032e25d0>
>>> MyArray([SelfishObject(), SelfishObject()]).sum()
MyArray(<__main__.SelfishObject object at 0x1032e2610>, dtype=object)

It seems I can force the behavior I want, using __array_wrap__:

class MyArray(np.ndarray):

    def __new__(cls, input_array):
        # cast to be our class type
        obj = np.asarray(input_array).view(cls)
        return obj

    def __array_finalize__(self, obj):
        pass

    def __array_wrap__(self, out_arr, context=None):
        if out_arr.ndim:
            return np.ndarray.__array_wrap__(self, out_arr, context)
>>> np.array([SelfishObject(), SelfishObject()]).sum()
<__main__.SelfishObject object at 0x1032e2610>
>>> MyArray([SelfishObject(), SelfishObject()]).sum()
<__main__.SelfishObject object at 0x1032e2590>
>>> np.array([1,2,3]).sum()
6
>>> MyArray([1,2,3]).sum()
6

... but I'm wondering whether this is the preferred/recommended way to deal with this, and whether guidelines for this issue should be added to the documentation.

One related potential unexpected behavior is that the exceptions raised in __array_wrap__ seem to get caught at a higher level, and therefore appear to be ignored. Even odder, raising in __array_wrap__ also solves the 0-dimensional array problem:

class MyArray(np.ndarray):

    def __new__(cls, input_array):
        # cast to be our class type
        obj = np.asarray(input_array).view(cls)
        return obj

    def __array_finalize__(self, obj):
        pass

    def __array_wrap__(self, out_arr, context=None):
        raise RuntimeWarning
>>> MyArray([SelfishObject(), SelfishObject()]).sum()
<__main__.SelfishObject object at 0x1032e2590>
>>> MyArray([1,2,3]).sum()
6

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions