<div class="gmail_quote">On Tue, Jun 21, 2011 at 12:36 PM, Charles R Harris <span dir="ltr"><<a href="mailto:charlesr.harris@gmail.com">charlesr.harris@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
<br><br><div class="gmail_quote"><div class="im">On Mon, Jun 20, 2011 at 12:32 PM, Mark Wiebe <span dir="ltr"><<a href="mailto:mwwiebe@gmail.com" target="_blank">mwwiebe@gmail.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

NumPy has a mechanism built in to allow subclasses to adjust or override aspects of the ufunc behavior. While this goal is important, this mechanism only allows for very limited customization, making for instance the masked arrays unable to work with the native ufuncs in a full and proper way. I would like to deprecate the current mechanism, in particular __array_prepare__ and __array_wrap__, and introduce a new method I will describe below. If you've ever used these mechanisms, please review this design to see if it meets your needs.<div>


<br></div><div><br></div></blockquote></div><div><br>The current approach is at a dead end, so something better needs to be done.<br> <br></div><div class="im"><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204, 204, 204);padding-left:1ex">

<div></div><div>Any class type which would like to override its behavior in ufuncs would define a method called _numpy_ufunc_, and optionally an attribute __array_priority__ as can already be done. The class which wins the priority battle gets its _numpy_ufunc_ function called as follows:</div>


<div><br></div><blockquote style="margin:0 0 0 40px;border:none;padding:0px"><div>return arr._numpy_ufunc_(current_ufunc, *args, **kwargs)</div></blockquote><br><div>To support this overloading, the ufunc would get a new support method, result_type, and there would be a new global function, broadcast_empty_like.</div>


<div><br></div><div>The function ufunc.empty_like behaves like the global np.result_type, but produces the output type or a tuple of output types specific to the ufunc, which may follow a different convention than regular arithmetic type promotion. This allows for a class to create an output array of the correct type to pass to the ufunc if it needs to be different than the default.</div>


<div><br></div><div>The function broadcast_empty_like is just like empty_like, but takes a list or tuple of arrays which are to be broadcast together for producing the output, instead of just one.</div><div><br></div></blockquote>

</div><div><br>How does the ufunc get called so it doesn't get caught in an endless loop? I like the proposed method if it can also be used for classes that don't subclass ndarray. Masked array, for instance, should probably not subclass ndarray.<br>
</div></div></blockquote><div><br></div><div>The function being called needs to ensure this, either by extracting a raw ndarray from instances of its class, or adding a 'subok = False' parameter to the kwargs. Supporting objects that aren't ndarray subclasses is one of the purposes for this approach, and neither of my two example cases subclassed ndarray.</div>
<div><br></div><div>-Mark</div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;"><div class="gmail_quote"><div>
 <br></div><div><div></div><div class="h5"><blockquote class="gmail_quote" style="margin:0pt 0pt 0pt 0.8ex;border-left:1px solid rgb(204, 204, 204);padding-left:1ex"><div></div><div>
Thanks,</div><div>Mark</div><div><br></div><div><br></div><div>A simple class which overrides the ufuncs might look as follows:</div><div><br></div><div><div><div>def sin(ufunc, *args, **kwargs):</div><div>    # Convert degrees to radians</div>


<div>    args[0] = np.deg2rad(args[0])</div><div>    # Return a regular array, since the result is not in degrees</div><div>    return ufunc(*args, **kwargs)</div><div><br></div><div>class MyDegreesClass:</div><div>    """Array-like object with a degrees unit"""</div>


<div><br></div><div>    def __init__(arr):</div><div>        self.arr = arr</div><div><br></div><div>    def _numpy_ufunc_(ufunc, *args, **kwargs):</div><div>        override = globals().get(<a href="http://ufunc.name" target="_blank">ufunc.name</a>)</div>


<div>        if override:</div><div>            return override(ufunc, *args, **kwargs)</div><div>        else:</div><div>            raise TypeError, 'ufunc %s incompatible with MyDegreesClass' % <a href="http://ufunc.name" target="_blank">ufunc.name</a></div>


</div></div><div><br></div><div><br></div><div><br></div><div>A more complex example will be something like this:</div><div><br></div><div><div>def my_general_ufunc(ufunc, *args, **kwargs):</div><div>    # Extract the 'out' argument. This only supports ufuncs with</div>


<div>    # one output, currently.</div><div>    out = kwargs.get('out')</div><div>    if len(args) > ufunc.nin:</div><div>        if out is None:</div><div>            out = args[ufunc.nin]</div><div>        else:</div>


<div>            raise ValueError, "'out' given as both a position and keyword argument"</div><div><br></div><div>    # Just want the inputs from here on</div><div>    args = args[:ufunc.nin]</div><div>

<br>
</div><div>    # Strip out MyArrayClass, but allow operations with regular ndarrays</div><div>    raw_in = []</div><div>    for a in args:</div><div>        if isinstance(a, MyArrayClass):</div><div>            raw_in.append(a.arr)</div>


<div>        else:</div><div>            raw_in.append(a)</div><div><br></div><div>    # Allocate the output array</div><div>    if not out is None:</div><div>        if isinstance(out, MyArrayClass):</div><div>            raise TypeError, "'out' must have type MyArrayClass"</div>


<div>    else:</div><div>        # Create the output array, obeying the 'order' parameter,</div><div>        # but disallowing subclasses</div><div>        out = np.broadcast_empty_like([args,</div><div>                                order=kwargs.get('order'),</div>


<div>                                dtype=ufunc.result_type(args),</div><div>                                subok=False)</div><div><br></div><div>    # Override the output argument</div><div>    kwargs['out'] = out.arr</div>


<div><br></div><div>    # Call the ufunc</div><div>    ufunc(*args, **kwargs)</div><div><br></div><div>    # Return the output</div><div>    return out</div><div><br></div><div>class MyArrayClass:</div><div>    def __init__(arr):</div>


<div>        self.arr = arr</div><div><br></div><div>    def _numpy_ufunc_(ufunc, *args, **kwargs):</div><div>        override = globals().get(<a href="http://ufunc.name" target="_blank">ufunc.name</a>)</div><div>        if override:</div>


<div>            return override(ufunc, *args, **kwargs)</div><div>        else:</div><div>            return my_general_ufunc(ufunc, *args, **kwargs)</div></div>
<br></blockquote></div></div><div><br>Chuck <br></div></div>
<br>_______________________________________________<br>
NumPy-Discussion mailing list<br>
<a href="mailto:NumPy-Discussion@scipy.org">NumPy-Discussion@scipy.org</a><br>
<a href="http://mail.scipy.org/mailman/listinfo/numpy-discussion" target="_blank">http://mail.scipy.org/mailman/listinfo/numpy-discussion</a><br>
<br></blockquote></div><br>