On Tue, Jun 21, 2011 at 12:36 PM, Charles R Harris < charlesr.har...@gmail.com> wrote:
> > > On Mon, Jun 20, 2011 at 12:32 PM, Mark Wiebe <mwwi...@gmail.com> wrote: > >> 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. >> >> >> > The current approach is at a dead end, so something better needs to be > done. > > >> 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: >> >> return arr._numpy_ufunc_(current_ufunc, *args, **kwargs) >> >> >> 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. >> >> 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. >> >> 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. >> >> > 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. > 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. -Mark > > >> Thanks, >> Mark >> >> >> A simple class which overrides the ufuncs might look as follows: >> >> def sin(ufunc, *args, **kwargs): >> # Convert degrees to radians >> args[0] = np.deg2rad(args[0]) >> # Return a regular array, since the result is not in degrees >> return ufunc(*args, **kwargs) >> >> class MyDegreesClass: >> """Array-like object with a degrees unit""" >> >> def __init__(arr): >> self.arr = arr >> >> def _numpy_ufunc_(ufunc, *args, **kwargs): >> override = globals().get(ufunc.name) >> if override: >> return override(ufunc, *args, **kwargs) >> else: >> raise TypeError, 'ufunc %s incompatible with MyDegreesClass' % >> ufunc.name >> >> >> >> A more complex example will be something like this: >> >> def my_general_ufunc(ufunc, *args, **kwargs): >> # Extract the 'out' argument. This only supports ufuncs with >> # one output, currently. >> out = kwargs.get('out') >> if len(args) > ufunc.nin: >> if out is None: >> out = args[ufunc.nin] >> else: >> raise ValueError, "'out' given as both a position and keyword >> argument" >> >> # Just want the inputs from here on >> args = args[:ufunc.nin] >> >> # Strip out MyArrayClass, but allow operations with regular ndarrays >> raw_in = [] >> for a in args: >> if isinstance(a, MyArrayClass): >> raw_in.append(a.arr) >> else: >> raw_in.append(a) >> >> # Allocate the output array >> if not out is None: >> if isinstance(out, MyArrayClass): >> raise TypeError, "'out' must have type MyArrayClass" >> else: >> # Create the output array, obeying the 'order' parameter, >> # but disallowing subclasses >> out = np.broadcast_empty_like([args, >> order=kwargs.get('order'), >> dtype=ufunc.result_type(args), >> subok=False) >> >> # Override the output argument >> kwargs['out'] = out.arr >> >> # Call the ufunc >> ufunc(*args, **kwargs) >> >> # Return the output >> return out >> >> class MyArrayClass: >> def __init__(arr): >> self.arr = arr >> >> def _numpy_ufunc_(ufunc, *args, **kwargs): >> override = globals().get(ufunc.name) >> if override: >> return override(ufunc, *args, **kwargs) >> else: >> return my_general_ufunc(ufunc, *args, **kwargs) >> >> > Chuck > > _______________________________________________ > NumPy-Discussion mailing list > NumPy-Discussion@scipy.org > http://mail.scipy.org/mailman/listinfo/numpy-discussion > >
_______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@scipy.org http://mail.scipy.org/mailman/listinfo/numpy-discussion