-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support proper custom class reflexive operator applied to xarray objects #9944
Comments
Thanks for opening your first issue here at xarray! Be sure to follow the issue template! |
Indeed, currently Xarray very aggressively attempts to take control of all binary arithmetic operations (by applying them to the wrapped Xarray should only attempt to do this for objects with an API that work like multi-dimensional arrays. I see at least two ways to determine this:
My inclination would be to try the second solution first (I think it's a little cleaner / more comprehensive) but if that doesn't work I would be OK to fall back to the first one. |
Thanks for looking into this Stephen, I agree that the second solution is cleaner / more comprehensive. However, as xarray is an already well-known library with a big user base, the second solution could be more likely to break codebase for existing users. Considering the following example (I admit that people unlikely to do this, just for the sake of a potential problematic scenario): import numpy as np
import xarray as xr
class DemoObj:
def __add__(self, other):
print(f'__add__ call: type={other.__class__}, value={other}')
if not isinstance(other, np.ndarray):
return NotImplemented
return 1 + other
obj = DemoObj()
da = xr.DataArray(np.arange(8))
print('result: ', obj + da)
It "works" now but would complain if xarray starts to check Also the checks could be more involved than just Example 1: from numpy.lib.mixins import NDArrayOperatorsMixin
class DemoObj(NDArrayOperatorsMixin):
def __array_ufunc__(
self,
ufunc,
method,
*inputs,
**kwargs,
):
if method == '__call__':
print(f'calling {ufunc}: {inputs=}, {kwargs=}')
obj = DemoObj()
da = xr.DataArray(np.arange(8))
print('result: ', obj + da)
for name in ['__array_ufunc__', '__array_function__', '__array_namespace__']:
print(f'hasattr({name}):', hasattr(obj, name))
Example 2: class DemoObj:
def __array__(self):
return np.array(10)
obj = DemoObj()
da = xr.DataArray(np.arange(8))
print('result: ', obj + da)
for name in ['__array_ufunc__', '__array_function__', '__array_namespace__']:
print(f'hasattr({name}):', hasattr(obj, name))
Besides, I think approach 1 (check obj_supports_xarray_binary_op = (
...
or (getattr(obj, '__array_ufunc__', None) is not None)
or ...
) If you agree with the above, I will make a PR with Please let me know what's your thought, many thanks. |
Is your feature request related to a problem?
I would like to implement reflexive operator on a custom class applied to xarray objects.
Following is a demo snippet:
Actual Output:
We can see
__add__
got called once and receivedxr.DataArray
obj but__radd__
got called 8 times and receivedint
s. This causes 2 problems;xr.DataArray
xr.DataArray
coords which is needed in a more realistic use caseDescribe the solution you'd like
I would like to have a mechanism so that
DemoObj.__radd__
got called only once and receivedxr.DataArray
instance in the above example.Describe alternatives you've considered
Option 1:
The most naive approach to workaround this is to call
obj.__radd__(da)
to achieveda + obj
which defeats the purpose of implementing the reflexive operator and not offer good readability.Option 2:
As
xr.DataArray._binary_op
replies on numpy's operator resolving mechanism under the hood, I could improve the situation by setting__array_ufunc__ = None
on my class, e.g.:This will make
__radd__
get called once withnp.ndarray
instead of 8 times withint
s. This solves the potential perf concern, however, it still doesn't cover the case ifxr.Dataarray.coords
is needed.Additional context
Considering
xr.DataArray._binary_op
has already returnedNoImplemented
for a list of classes:https://github.com/pydata/xarray/blob/v2025.01.1/xarray/core/dataarray.py#L4808-L4809
I'm wondering whether we should do the same for classes has
__array_ufunc__ = None
, i.e.:I'm happy with a similar property if you prefer to make it xarray specific. I'm happy to make the PR as well once you confirmed the mechanism / property name you preferred.
Many thanks in advance!
The text was updated successfully, but these errors were encountered: