Why doesn't my class inheriting from array.array work?

Viewed 100

I want to inherit from the array.array class, and here's my code.

import array
a = array.array('d', [1.0, 2.0, 3.14])

import numpy as np

class diff_rle_array(array.array):
    def __init__(self, arr: array):
        self._internal = np.ediff1d(arr)


diff_rle_array(a)

But it gives an error

>>> diff_rle_array(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: array() argument 1 must be a unicode character, not array.array

But I have overridden the __init__ method. Is it because array.array is in the base library and hence special?

2 Answers

The storage of C-implemented builtins is not a named attribute, it's integrated into the methods and initializers of the type itself. _internal means nothing to array.array, it's just a random attribute you attached to it that is used by none of its methods. If you want to store the values where array.array stores them (so the methods use them implicitly), delegate up to the parent class. In this case, the real work in array.array is done in __new__ (the constructor), not __init__ (the initializer), which means you need to explicitly construct and return:

class diff_rle_array(array.array):
    __slots__ = ()  # Not necessary, but avoids creation of __dict__ for arbitrary attributes, saving memory
    def __new__(cls, arr: array):  # __new__ receives the class, not an instance
        return super().__new__(cls, 'd', np.ediff1d(arr))  # You must return the new instance created by delegating to parent,
                                                           # passing all the arguments it expects

Having done that, your expected results are produced:

>>> a = array.array('d', [1.0, 2.0, 3.14])
>>> diff_rle_array(a)
diff_rle_array('d', [1.0, 1.1400000000000001])

__new__ is called before __init__, with the same arguments.

diff_rle_array.__new__ is not defined, so array.array.__new__ gets used.

>>> diff_rle_array.__new__
<function array.__new__(*args, **kwargs)>

Now, since you have deviated from the parent class signature in the constructor (see Liskov substitution principle), the array.__new__ receives unexpected arguments - an array instance is sent in, in place of a typecode. That is what the error message means:

TypeError: array() argument 1 must be a unicode character, not array.array

It is not necessarily always wrong to violate LSP, but you would need to override __new__ as well.

Related