Skip to content

Commit 9f5c749

Browse files
authored
REF: implement helpers for __array_ufunc__ (pandas-dev#43856)
1 parent bd95c6e commit 9f5c749

File tree

2 files changed

+77
-4
lines changed

2 files changed

+77
-4
lines changed

pandas/core/arraylike.py

+76-4
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,8 @@ def array_ufunc(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any)
244244

245245
cls = type(self)
246246

247+
kwargs = _standardize_out_kwarg(**kwargs)
248+
247249
# for backwards compatibility check and potentially fallback for non-aligned frames
248250
result = _maybe_fallback(ufunc, method, *inputs, **kwargs)
249251
if result is not NotImplemented:
@@ -318,6 +320,11 @@ def array_ufunc(self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any)
318320
def reconstruct(result):
319321
if lib.is_scalar(result):
320322
return result
323+
324+
if isinstance(result, tuple):
325+
# np.modf, np.frexp, np.divmod
326+
return tuple(reconstruct(x) for x in result)
327+
321328
if result.ndim != self.ndim:
322329
if method == "outer":
323330
if self.ndim == 2:
@@ -349,6 +356,13 @@ def reconstruct(result):
349356
result = result.__finalize__(self)
350357
return result
351358

359+
if "out" in kwargs:
360+
result = _dispatch_ufunc_with_out(self, ufunc, method, *inputs, **kwargs)
361+
return reconstruct(result)
362+
363+
# We still get here with kwargs `axis` for e.g. np.maximum.accumulate
364+
# and `dtype` and `keepdims` for np.ptp
365+
352366
if self.ndim > 1 and (len(inputs) > 1 or ufunc.nout > 1):
353367
# Just give up on preserving types in the complex case.
354368
# In theory we could preserve them for them.
@@ -375,8 +389,66 @@ def reconstruct(result):
375389
# Those can have an axis keyword and thus can't be called block-by-block
376390
result = getattr(ufunc, method)(np.asarray(inputs[0]), **kwargs)
377391

378-
if ufunc.nout > 1:
379-
result = tuple(reconstruct(x) for x in result)
380-
else:
381-
result = reconstruct(result)
392+
result = reconstruct(result)
382393
return result
394+
395+
396+
def _standardize_out_kwarg(**kwargs) -> dict:
397+
"""
398+
If kwargs contain "out1" and "out2", replace that with a tuple "out"
399+
400+
np.divmod, np.modf, np.frexp can have either `out=(out1, out2)` or
401+
`out1=out1, out2=out2)`
402+
"""
403+
if "out" not in kwargs and "out1" in kwargs and "out2" in kwargs:
404+
out1 = kwargs.pop("out1")
405+
out2 = kwargs.pop("out2")
406+
out = (out1, out2)
407+
kwargs["out"] = out
408+
return kwargs
409+
410+
411+
def _dispatch_ufunc_with_out(self, ufunc: np.ufunc, method: str, *inputs, **kwargs):
412+
"""
413+
If we have an `out` keyword, then call the ufunc without `out` and then
414+
set the result into the given `out`.
415+
"""
416+
417+
# Note: we assume _standardize_out_kwarg has already been called.
418+
out = kwargs.pop("out")
419+
where = kwargs.pop("where", None)
420+
421+
result = getattr(ufunc, method)(*inputs, **kwargs)
422+
423+
if result is NotImplemented:
424+
return NotImplemented
425+
426+
if isinstance(result, tuple):
427+
# i.e. np.divmod, np.modf, np.frexp
428+
if not isinstance(out, tuple) or len(out) != len(result):
429+
raise NotImplementedError
430+
431+
for arr, res in zip(out, result):
432+
_assign_where(arr, res, where)
433+
434+
return out
435+
436+
if isinstance(out, tuple):
437+
if len(out) == 1:
438+
out = out[0]
439+
else:
440+
raise NotImplementedError
441+
442+
_assign_where(out, result, where)
443+
return out
444+
445+
446+
def _assign_where(out, result, where) -> None:
447+
"""
448+
Set a ufunc result into 'out', masking with a 'where' argument if necessary.
449+
"""
450+
if where is None:
451+
# no 'where' arg passed to ufunc
452+
out[:] = result
453+
else:
454+
np.putmask(out, where, result)

pandas/core/generic.py

+1
Original file line numberDiff line numberDiff line change
@@ -2100,6 +2100,7 @@ def __array_wrap__(
21002100
self, method="__array_wrap__"
21012101
)
21022102

2103+
@final
21032104
def __array_ufunc__(
21042105
self, ufunc: np.ufunc, method: str, *inputs: Any, **kwargs: Any
21052106
):

0 commit comments

Comments
 (0)