inheriting numpy.vectorize-ed functions
Question: While hunting down an obscure bug, I've stumbled onto something best
demonstrated by this minimal example:

    
    
    import numpy as np
    
    class First(object):
        def __init__(self):
            self.vF = np.vectorize(self.F)
            print "First: vF = ", self.vF
    
        def F(self, x):
            return x**2
    
    
    class Second(First):
        def __init__(self):
            super(Second, self).__init__()
            print "Second: vF = ", self.vF
    
        def F(self, x):
            raise RuntimeError("Never be here.")
    
        def vF(self, x):
            return np.asarray(x)*2
    

I'd expect that an instance of the `Second` would have an explicitly defined
`vF` method, but that does not seem to be the case:

    
    
    arg = (1, 2, 3)
    
    f = First()       
    print "calling first.vF: ", f.vF(arg)
    
    s = Second()
    print "calling second.vF: ", s.vF(arg)
    

produces

    
    
    First: vF =  <numpy.lib.function_base.vectorize object at 0x23f9310>
    calling first.vF:  [1 4 9]
    First: vF =  <numpy.lib.function_base.vectorize object at 0x23f93d0>
    Second: vF =  <numpy.lib.function_base.vectorize object at 0x23f93d0>
    calling second.vF: 
    Traceback (most recent call last):
    ...
    RuntimeError: Never be here.
    

so that it seems that `s.vF` and `f.vF` is the same object, even though `s.vF
== f.vF` is `False`.

Is this an expected/known/documented behavior, and `numpy.vectorize` does not
play nicely with inheritance, or am I missing something simple here? (sure, in
this particular case the problem is easy to fix by either changing `First.vF`
to a normal Python method, or just not calling `super` in the `Second`'s
constructor.)


Answer: This has nothing to do with NumPy. It is a consequence of the interaction of
perfectly reasonable language design decisions (and the way you decided to use
the language):

  * Instance attributes take precedence over class attributes. I'm sure you'll agree that this is reasonable.
  * Methods are class attributes, and not special at that. I'm sure you'll agree that this is reasonable (if you don't, look into descriptors, specifically bound methods which allows `self.F` to work).
  * Inherited instance attributes are attached to the same object, not to some weird "parent proxy" object or something. I'm sure you'll agree that this is reasonable.

In combination, these perfectly reasonable behaviors may yield unexpected
behavior, if you do not keep the details in mind and instead work with a
simplified mental model (e.g. mentally segregating methods and "data"
attributes). In detail, this happens in your example:

  * The respective constructor is called. This is either `First.__init__`, or `Second.__init__` which immediately calls `First.__init__`.
  * Therefore, `obj.vF` is _always_ the vectorized function created in `First.__init__` for all `obj`.
  * However, each object's vectorized function wraps the `self.F` of the respective object. In the case of the second object, this is the `RuntimeError`-raising `Second.F`.

You should probably just use a regular `vF` method here, as this allows easy
overriding by subclasses, due to the way attribute lookup works (see also:
MRO).

