Search code examples
python-2.7classmethodssuperclass

Magic method __repr__ leads to AttributeError with __new__ method


My goal is to give numpy.ndarray a different representation, since I want to represent some arrays with units. Thus, I programmed a class that inherits its attributes/ methods from numpy.ndarray. For the another representation I wanted to use the __repr__ magic method like:

class Quantitiy(np.ndarray):
    def __new__(cls, value, unit=None, dtype=None, copy=True, order=None, subok=False, ndmin=0):

        value = np.asarray(value)

        obj = np.array(value, dtype=dtype, copy=copy, order=order, 
                       subok=True, ndmin=ndmin).view(cls)

        obj.__unit = util.def_unit(unit)
        obj.__value = value

        return obj

    def __repr__(self):
        prefix = '<{0} '.format(self.__class__.__name__)
        sep = ','
        arrstr = np.array2string(self.view(np.ndarray), 
                                 separator=sep,
                                 prefix=prefix)

        return '{0}{1} {2}>'.format(prefix, arrstr, self.__unit)

So far this works fine. However, if I want to access the inherited methods from numpy.ndarray I get a AttributeError because __repr__ cant resolve self.__unit.

I tried to solve this problem with a private method that defines the variable self.__unit and called it within the __new__ method but without success:

class Quantitiy(np.ndarray):
    def __new__(cls, value, unit=None, dtype=None, copy=True, order=None, subok=False, ndmin=0):

        value = np.asarray(value)

        obj = np.array(value, dtype=dtype, copy=copy, order=order, subok=True, ndmin=ndmin).view(cls)

        # Here I call the private method to initialize self.__unit.
        obj.__set_unit()
        obj.__value = value

        return obj

    def __repr__(self):
        prefix = '<{0} '.format(self.__class__.__name__)
        sep = ','
        arrstr = np.array2string(self.view(np.ndarray), separator=sep, prefix=prefix)

        return '{0}{1} {2}>'.format(prefix, arrstr, self.__unit)

    # New defined private class.
    def __set_unit(self, unit):
        self.__unit = util.def_unit(unit)

I can not solve this with something like cls.__unit = util.def_unit(unit) in the __new__ method. I already tried to define a __init__ method after __new__. Moreover, I tried to interchange the private methods with public methods.

What I expect:

>>> array = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
>>> q = Quantity(value, unit="meter / second")
>>> q
    <Quantitiy [[1,2,3,4],
                [5,6,7,8]] meter/second>
>>> q * q
>>> <Quantitiy [[ 1, 4, 9,16],
                [25,36,49,64]] meter**2/second**2>

>>> q.min()
>>> <Quantitiy 1 meter/second>

The actual result is:

>>> array = np.array([[1, 2, 3, 4], [5, 6, 7, 8]])
>>> q = Quantity(value, unit="meter / second")
>>> q
    <Quantitiy [[1,2,3,4],
                [5,6,7,8]] meter/second>
>>> q * q
>>> <Quantitiy [[ 1, 4, 9,16],
                [25,36,49,64]] meter**2/second**2>

# Up to here everything works fine.

>>> q.min()
>>> AttributeError: 'Quantitiy' object has no attribute 
    '_Quantitiy__unit'

Does anyone see the mistake and can help me?


Solution

  • Ok, the answer is - as usual - in the FineManual (and could be found searching for "subclassing numpy ndarray" - which is how I found it actually), and requires implementing __array_finalize__(self, obj) :

    import numpy as np
    
    class Quantitiy(np.ndarray):
        def __new__(cls, value, unit=None, dtype=None, copy=True, order=None, subok=False, ndmin=0):
    
            value = np.asarray(value)
            x = np.array(value, dtype=dtype, copy=copy, order=order, subok=True, ndmin=ndmin)
            obj = x.view(type=cls)
            obj._unit = unit
            obj._value = value
            return obj
    
        def __repr__(self):
            print("repr %s" % type(self))
            prefix = '<{0} '.format(self.__class__.__name__)
            sep = ','
            arrstr = np.array2string(self.view(np.ndarray), 
                                     separator=sep,
                                     prefix=prefix)
    
            return '{0}{1} {2}>'.format(prefix, arrstr, self._unit)
    
    
        def __array_finalize__(self, obj):
            # see InfoArray.__array_finalize__ for comments
            if obj is None:
                return
            self._unit = getattr(obj, '_unit', None)
            self._value = getattr(obj, '_value', None)