Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclassing numpy.ndarray - why is __array_finalize__ not being called twice here?

According to this primer on subclassing ndarray, the __array_finalize__ method is guaranteed to be called, no matter if the subclass is instantiated directly, casted as a view or created from a template.

In particular, when calling the constructor explicitly, the order of methods called is __new__ -> __array_finalize__ -> __init__.

I have the following simple subclass of ndarray which allows an additional title attribute.

class Frame(np.ndarray):    
    def __new__(cls, input_array, title='unnamed'):
        print 'calling Frame.__new__ with title {}'.format(title)
        self = input_array.view(Frame) # does not call __new__ or __init__
        print 'creation of self done, setting self.title...'
        self.title = title
        return self

    def __array_finalize__(self, viewed):
        # if viewed is None, the Frame instance is being created by an explicit
        # call to the constructor, hence Frame.__new__ has been called and the
        # title attribute is already set
        #
        # if viewed is not None, the frame is either being created by view
        # casting or from template, in which case the title of the viewed object
        # needs to be forwarded to the new instance
        print '''calling Frame.__array_finalize__ with type(self) == {} and
        type(viewed) == {}'''.format(type(self), type(viewed))

        if viewed is not None:
            self.title = getattr(viewed, 'title', 'unnamed')

        print self.title

which produces the following output:

>>> f = Frame(np.arange(3), 'hallo')
calling Frame.__new__ with title hallo
calling Frame.__array_finalize__ with type(self) == <class '__main__.Frame'> and
        type(viewed) == <type 'numpy.ndarray'>
unnamed
creation of self done, setting self.title...
>>> f.title
'hallo'

As you can see, __array_finalize__ is being called as a result of the line

 self = input_array.view(Frame)

Question: why is __array_finalize__ not being called again as part of the __new__ -> __array_finalize__ -> __init__ chain?

like image 497
timgeb Avatar asked Sep 27 '16 18:09

timgeb


1 Answers

In the documentation you link to, it describes how ndarray.__new__ will call __array_finalize__ on the arrays it constructs. And your class's __new__ method is causing that to happen when you create your instance as a view of an existing array. The view method on the array argument is calling ndarray.__new__ for you, and it calls your __array_finalize__ method before the instance is returned to you.

You don't see __array_finalize__ called twice because you're not calling ndarray.__new__ a second time. If your __new__ method included a call to super().__new__ in addition to the view call, you probably would see __array_finalized__ called twice. Such behavior would probably be buggy (or at least, slower than necessary), so it's no surprise you're not doing it!

Python doesn't automatically call overridden methods when the overriding subclass's method gets called. It's up to the overriding method to call (or not call) the overridden version (directly with super or, as in this case, indirectly via another object's view method).

like image 65
Blckknght Avatar answered Nov 06 '22 00:11

Blckknght