Skip to content

Commit fd6d45e

Browse files
jbrockmendelNico Cernek
authored and
Nico Cernek
committed
REF: separate out dispatch-centric ops functions (pandas-dev#28624)
1 parent 2e5c399 commit fd6d45e

File tree

3 files changed

+231
-232
lines changed

3 files changed

+231
-232
lines changed

pandas/core/ops/__init__.py

+6-225
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,27 @@
55
"""
66
import datetime
77
import operator
8-
from typing import Any, Callable, Tuple, Union
8+
from typing import Tuple
99

1010
import numpy as np
1111

1212
from pandas._libs import Timedelta, Timestamp, lib
13-
from pandas.errors import NullFrequencyError
1413
from pandas.util._decorators import Appender
1514

16-
from pandas.core.dtypes.common import (
17-
is_datetime64_dtype,
18-
is_extension_array_dtype,
19-
is_integer_dtype,
20-
is_list_like,
21-
is_object_dtype,
22-
is_scalar,
23-
is_timedelta64_dtype,
24-
)
25-
from pandas.core.dtypes.generic import (
26-
ABCDataFrame,
27-
ABCExtensionArray,
28-
ABCIndexClass,
29-
ABCSeries,
30-
)
15+
from pandas.core.dtypes.common import is_list_like, is_timedelta64_dtype
16+
from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries
3117
from pandas.core.dtypes.missing import isna
3218

33-
from pandas._typing import ArrayLike
34-
from pandas.core.construction import array, extract_array
19+
from pandas.core.construction import extract_array
3520
from pandas.core.ops.array_ops import (
3621
arithmetic_op,
3722
comparison_op,
3823
define_na_arithmetic_op,
3924
logical_op,
4025
)
4126
from pandas.core.ops.array_ops import comp_method_OBJECT_ARRAY # noqa:F401
27+
from pandas.core.ops.dispatch import maybe_dispatch_ufunc_to_dunder_op # noqa:F401
28+
from pandas.core.ops.dispatch import should_series_dispatch
4229
from pandas.core.ops.docstrings import (
4330
_arith_doc_FRAME,
4431
_flex_comp_doc_FRAME,
@@ -358,71 +345,6 @@ def fill_binop(left, right, fill_value):
358345
# Dispatch logic
359346

360347

361-
def should_extension_dispatch(left: ABCSeries, right: Any) -> bool:
362-
"""
363-
Identify cases where Series operation should use dispatch_to_extension_op.
364-
365-
Parameters
366-
----------
367-
left : Series
368-
right : object
369-
370-
Returns
371-
-------
372-
bool
373-
"""
374-
if (
375-
is_extension_array_dtype(left.dtype)
376-
or is_datetime64_dtype(left.dtype)
377-
or is_timedelta64_dtype(left.dtype)
378-
):
379-
return True
380-
381-
if not is_scalar(right) and is_extension_array_dtype(right):
382-
# GH#22378 disallow scalar to exclude e.g. "category", "Int64"
383-
return True
384-
385-
return False
386-
387-
388-
def should_series_dispatch(left, right, op):
389-
"""
390-
Identify cases where a DataFrame operation should dispatch to its
391-
Series counterpart.
392-
393-
Parameters
394-
----------
395-
left : DataFrame
396-
right : DataFrame
397-
op : binary operator
398-
399-
Returns
400-
-------
401-
override : bool
402-
"""
403-
if left._is_mixed_type or right._is_mixed_type:
404-
return True
405-
406-
if not len(left.columns) or not len(right.columns):
407-
# ensure obj.dtypes[0] exists for each obj
408-
return False
409-
410-
ldtype = left.dtypes.iloc[0]
411-
rdtype = right.dtypes.iloc[0]
412-
413-
if (is_timedelta64_dtype(ldtype) and is_integer_dtype(rdtype)) or (
414-
is_timedelta64_dtype(rdtype) and is_integer_dtype(ldtype)
415-
):
416-
# numpy integer dtypes as timedelta64 dtypes in this scenario
417-
return True
418-
419-
if is_datetime64_dtype(ldtype) and is_object_dtype(rdtype):
420-
# in particular case where right is an array of DateOffsets
421-
return True
422-
423-
return False
424-
425-
426348
def dispatch_to_series(left, right, func, str_rep=None, axis=None):
427349
"""
428350
Evaluate the frame operation func(left, right) by evaluating
@@ -489,58 +411,6 @@ def column_op(a, b):
489411
return new_data
490412

491413

492-
def dispatch_to_extension_op(
493-
op,
494-
left: Union[ABCExtensionArray, np.ndarray],
495-
right: Any,
496-
keep_null_freq: bool = False,
497-
):
498-
"""
499-
Assume that left or right is a Series backed by an ExtensionArray,
500-
apply the operator defined by op.
501-
502-
Parameters
503-
----------
504-
op : binary operator
505-
left : ExtensionArray or np.ndarray
506-
right : object
507-
keep_null_freq : bool, default False
508-
Whether to re-raise a NullFrequencyError unchanged, as opposed to
509-
catching and raising TypeError.
510-
511-
Returns
512-
-------
513-
ExtensionArray or np.ndarray
514-
2-tuple of these if op is divmod or rdivmod
515-
"""
516-
# NB: left and right should already be unboxed, so neither should be
517-
# a Series or Index.
518-
519-
if left.dtype.kind in "mM" and isinstance(left, np.ndarray):
520-
# We need to cast datetime64 and timedelta64 ndarrays to
521-
# DatetimeArray/TimedeltaArray. But we avoid wrapping others in
522-
# PandasArray as that behaves poorly with e.g. IntegerArray.
523-
left = array(left)
524-
525-
# The op calls will raise TypeError if the op is not defined
526-
# on the ExtensionArray
527-
528-
try:
529-
res_values = op(left, right)
530-
except NullFrequencyError:
531-
# DatetimeIndex and TimedeltaIndex with freq == None raise ValueError
532-
# on add/sub of integers (or int-like). We re-raise as a TypeError.
533-
if keep_null_freq:
534-
# TODO: remove keep_null_freq after Timestamp+int deprecation
535-
# GH#22535 is enforced
536-
raise
537-
raise TypeError(
538-
"incompatible type for a datetime/timedelta "
539-
"operation [{name}]".format(name=op.__name__)
540-
)
541-
return res_values
542-
543-
544414
# -----------------------------------------------------------------------------
545415
# Series
546416

@@ -906,92 +776,3 @@ def f(self, other):
906776
f.__name__ = op_name
907777

908778
return f
909-
910-
911-
# -----------------------------------------------------------------------------
912-
# Sparse
913-
914-
915-
def maybe_dispatch_ufunc_to_dunder_op(
916-
self: ArrayLike, ufunc: Callable, method: str, *inputs: ArrayLike, **kwargs: Any
917-
):
918-
"""
919-
Dispatch a ufunc to the equivalent dunder method.
920-
921-
Parameters
922-
----------
923-
self : ArrayLike
924-
The array whose dunder method we dispatch to
925-
ufunc : Callable
926-
A NumPy ufunc
927-
method : {'reduce', 'accumulate', 'reduceat', 'outer', 'at', '__call__'}
928-
inputs : ArrayLike
929-
The input arrays.
930-
kwargs : Any
931-
The additional keyword arguments, e.g. ``out``.
932-
933-
Returns
934-
-------
935-
result : Any
936-
The result of applying the ufunc
937-
"""
938-
# special has the ufuncs we dispatch to the dunder op on
939-
special = {
940-
"add",
941-
"sub",
942-
"mul",
943-
"pow",
944-
"mod",
945-
"floordiv",
946-
"truediv",
947-
"divmod",
948-
"eq",
949-
"ne",
950-
"lt",
951-
"gt",
952-
"le",
953-
"ge",
954-
"remainder",
955-
"matmul",
956-
}
957-
aliases = {
958-
"subtract": "sub",
959-
"multiply": "mul",
960-
"floor_divide": "floordiv",
961-
"true_divide": "truediv",
962-
"power": "pow",
963-
"remainder": "mod",
964-
"divide": "div",
965-
"equal": "eq",
966-
"not_equal": "ne",
967-
"less": "lt",
968-
"less_equal": "le",
969-
"greater": "gt",
970-
"greater_equal": "ge",
971-
}
972-
973-
# For op(., Array) -> Array.__r{op}__
974-
flipped = {
975-
"lt": "__gt__",
976-
"le": "__ge__",
977-
"gt": "__lt__",
978-
"ge": "__le__",
979-
"eq": "__eq__",
980-
"ne": "__ne__",
981-
}
982-
983-
op_name = ufunc.__name__
984-
op_name = aliases.get(op_name, op_name)
985-
986-
def not_implemented(*args, **kwargs):
987-
return NotImplemented
988-
989-
if method == "__call__" and op_name in special and kwargs.get("out") is None:
990-
if isinstance(inputs[0], type(self)):
991-
name = "__{}__".format(op_name)
992-
return getattr(self, name, not_implemented)(inputs[1])
993-
else:
994-
name = flipped.get(op_name, "__r{}__".format(op_name))
995-
return getattr(self, name, not_implemented)(inputs[0])
996-
else:
997-
return NotImplemented

pandas/core/ops/array_ops.py

+2-7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
from pandas.core.construction import extract_array
3838
from pandas.core.ops import missing
39+
from pandas.core.ops.dispatch import dispatch_to_extension_op, should_extension_dispatch
3940
from pandas.core.ops.invalid import invalid_comparison
4041
from pandas.core.ops.roperator import rpow
4142

@@ -179,11 +180,7 @@ def arithmetic_op(
179180
Or a 2-tuple of these in the case of divmod or rdivmod.
180181
"""
181182

182-
from pandas.core.ops import (
183-
maybe_upcast_for_op,
184-
should_extension_dispatch,
185-
dispatch_to_extension_op,
186-
)
183+
from pandas.core.ops import maybe_upcast_for_op
187184

188185
keep_null_freq = isinstance(
189186
right,
@@ -236,7 +233,6 @@ def comparison_op(
236233
-------
237234
ndarrray or ExtensionArray
238235
"""
239-
from pandas.core.ops import should_extension_dispatch, dispatch_to_extension_op
240236

241237
# NB: We assume extract_array has already been called on left and right
242238
lvalues = left
@@ -335,7 +331,6 @@ def logical_op(
335331
-------
336332
ndarrray or ExtensionArray
337333
"""
338-
from pandas.core.ops import should_extension_dispatch, dispatch_to_extension_op
339334

340335
fill_int = lambda x: x
341336

0 commit comments

Comments
 (0)