Skip to content

Commit b50ffc8

Browse files
authored
REF/TYP: use OpsMixin for arithmetic methods (#36994)
1 parent 6803e7d commit b50ffc8

File tree

7 files changed

+166
-122
lines changed

7 files changed

+166
-122
lines changed

pandas/core/arraylike.py

+70
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,73 @@ def __xor__(self, other):
7272
@unpack_zerodim_and_defer("__rxor__")
7373
def __rxor__(self, other):
7474
return self._logical_method(other, roperator.rxor)
75+
76+
# -------------------------------------------------------------
77+
# Arithmetic Methods
78+
79+
def _arith_method(self, other, op):
80+
return NotImplemented
81+
82+
@unpack_zerodim_and_defer("__add__")
83+
def __add__(self, other):
84+
return self._arith_method(other, operator.add)
85+
86+
@unpack_zerodim_and_defer("__radd__")
87+
def __radd__(self, other):
88+
return self._arith_method(other, roperator.radd)
89+
90+
@unpack_zerodim_and_defer("__sub__")
91+
def __sub__(self, other):
92+
return self._arith_method(other, operator.sub)
93+
94+
@unpack_zerodim_and_defer("__rsub__")
95+
def __rsub__(self, other):
96+
return self._arith_method(other, roperator.rsub)
97+
98+
@unpack_zerodim_and_defer("__mul__")
99+
def __mul__(self, other):
100+
return self._arith_method(other, operator.mul)
101+
102+
@unpack_zerodim_and_defer("__rmul__")
103+
def __rmul__(self, other):
104+
return self._arith_method(other, roperator.rmul)
105+
106+
@unpack_zerodim_and_defer("__truediv__")
107+
def __truediv__(self, other):
108+
return self._arith_method(other, operator.truediv)
109+
110+
@unpack_zerodim_and_defer("__rtruediv__")
111+
def __rtruediv__(self, other):
112+
return self._arith_method(other, roperator.rtruediv)
113+
114+
@unpack_zerodim_and_defer("__floordiv__")
115+
def __floordiv__(self, other):
116+
return self._arith_method(other, operator.floordiv)
117+
118+
@unpack_zerodim_and_defer("__rfloordiv")
119+
def __rfloordiv__(self, other):
120+
return self._arith_method(other, roperator.rfloordiv)
121+
122+
@unpack_zerodim_and_defer("__mod__")
123+
def __mod__(self, other):
124+
return self._arith_method(other, operator.mod)
125+
126+
@unpack_zerodim_and_defer("__rmod__")
127+
def __rmod__(self, other):
128+
return self._arith_method(other, roperator.rmod)
129+
130+
@unpack_zerodim_and_defer("__divmod__")
131+
def __divmod__(self, other):
132+
return self._arith_method(other, divmod)
133+
134+
@unpack_zerodim_and_defer("__rdivmod__")
135+
def __rdivmod__(self, other):
136+
return self._arith_method(other, roperator.rdivmod)
137+
138+
@unpack_zerodim_and_defer("__pow__")
139+
def __pow__(self, other):
140+
return self._arith_method(other, operator.pow)
141+
142+
@unpack_zerodim_and_defer("__rpow__")
143+
def __rpow__(self, other):
144+
return self._arith_method(other, roperator.rpow)

pandas/core/indexes/base.py

+7-20
Original file line numberDiff line numberDiff line change
@@ -5406,29 +5406,17 @@ def _cmp_method(self, other, op):
54065406

54075407
return result
54085408

5409-
@classmethod
5410-
def _add_numeric_methods_binary(cls):
5409+
def _arith_method(self, other, op):
54115410
"""
5412-
Add in numeric methods.
5411+
Wrapper used to dispatch arithmetic operations.
54135412
"""
5414-
setattr(cls, "__add__", _make_arithmetic_op(operator.add, cls))
5415-
setattr(cls, "__radd__", _make_arithmetic_op(ops.radd, cls))
5416-
setattr(cls, "__sub__", _make_arithmetic_op(operator.sub, cls))
5417-
setattr(cls, "__rsub__", _make_arithmetic_op(ops.rsub, cls))
5418-
setattr(cls, "__rpow__", _make_arithmetic_op(ops.rpow, cls))
5419-
setattr(cls, "__pow__", _make_arithmetic_op(operator.pow, cls))
54205413

5421-
setattr(cls, "__truediv__", _make_arithmetic_op(operator.truediv, cls))
5422-
setattr(cls, "__rtruediv__", _make_arithmetic_op(ops.rtruediv, cls))
5414+
from pandas import Series
54235415

5424-
setattr(cls, "__mod__", _make_arithmetic_op(operator.mod, cls))
5425-
setattr(cls, "__rmod__", _make_arithmetic_op(ops.rmod, cls))
5426-
setattr(cls, "__floordiv__", _make_arithmetic_op(operator.floordiv, cls))
5427-
setattr(cls, "__rfloordiv__", _make_arithmetic_op(ops.rfloordiv, cls))
5428-
setattr(cls, "__divmod__", _make_arithmetic_op(divmod, cls))
5429-
setattr(cls, "__rdivmod__", _make_arithmetic_op(ops.rdivmod, cls))
5430-
setattr(cls, "__mul__", _make_arithmetic_op(operator.mul, cls))
5431-
setattr(cls, "__rmul__", _make_arithmetic_op(ops.rmul, cls))
5416+
result = op(Series(self), other)
5417+
if isinstance(result, tuple):
5418+
return (Index(result[0]), Index(result[1]))
5419+
return Index(result)
54325420

54335421
@classmethod
54345422
def _add_numeric_methods_unary(cls):
@@ -5453,7 +5441,6 @@ def _evaluate_numeric_unary(self):
54535441
@classmethod
54545442
def _add_numeric_methods(cls):
54555443
cls._add_numeric_methods_unary()
5456-
cls._add_numeric_methods_binary()
54575444

54585445
def any(self, *args, **kwargs):
54595446
"""

pandas/core/indexes/range.py

+16-36
Original file line numberDiff line numberDiff line change
@@ -811,16 +811,13 @@ def any(self, *args, **kwargs) -> bool:
811811

812812
# --------------------------------------------------------------------
813813

814-
def _arith_method(self, other, op, step=False):
814+
def _arith_method(self, other, op):
815815
"""
816816
Parameters
817817
----------
818818
other : Any
819819
op : callable that accepts 2 params
820820
perform the binary op
821-
step : callable, optional, default to False
822-
op to apply to the step parm if not None
823-
if False, use the existing step
824821
"""
825822

826823
if isinstance(other, ABCTimedeltaIndex):
@@ -834,6 +831,21 @@ def _arith_method(self, other, op, step=False):
834831
# Must be an np.ndarray; GH#22390
835832
return op(self._int64index, other)
836833

834+
if op in [
835+
operator.pow,
836+
ops.rpow,
837+
operator.mod,
838+
ops.rmod,
839+
ops.rfloordiv,
840+
divmod,
841+
ops.rdivmod,
842+
]:
843+
return op(self._int64index, other)
844+
845+
step = False
846+
if op in [operator.mul, ops.rmul, operator.truediv, ops.rtruediv]:
847+
step = op
848+
837849
other = extract_array(other, extract_numpy=True)
838850
attrs = self._get_attributes_dict()
839851

@@ -871,35 +883,3 @@ def _arith_method(self, other, op, step=False):
871883
# Defer to Int64Index implementation
872884
return op(self._int64index, other)
873885
# TODO: Do attrs get handled reliably?
874-
875-
@unpack_zerodim_and_defer("__add__")
876-
def __add__(self, other):
877-
return self._arith_method(other, operator.add)
878-
879-
@unpack_zerodim_and_defer("__radd__")
880-
def __radd__(self, other):
881-
return self._arith_method(other, ops.radd)
882-
883-
@unpack_zerodim_and_defer("__sub__")
884-
def __sub__(self, other):
885-
return self._arith_method(other, operator.sub)
886-
887-
@unpack_zerodim_and_defer("__rsub__")
888-
def __rsub__(self, other):
889-
return self._arith_method(other, ops.rsub)
890-
891-
@unpack_zerodim_and_defer("__mul__")
892-
def __mul__(self, other):
893-
return self._arith_method(other, operator.mul, step=operator.mul)
894-
895-
@unpack_zerodim_and_defer("__rmul__")
896-
def __rmul__(self, other):
897-
return self._arith_method(other, ops.rmul, step=ops.rmul)
898-
899-
@unpack_zerodim_and_defer("__truediv__")
900-
def __truediv__(self, other):
901-
return self._arith_method(other, operator.truediv, step=operator.truediv)
902-
903-
@unpack_zerodim_and_defer("__rtruediv__")
904-
def __rtruediv__(self, other):
905-
return self._arith_method(other, ops.rtruediv, step=ops.rtruediv)

pandas/core/ops/__init__.py

+1-25
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@
1919
from pandas.core.dtypes.missing import isna
2020

2121
from pandas.core import algorithms
22-
from pandas.core.construction import extract_array
2322
from pandas.core.ops.array_ops import ( # noqa:F401
2423
arithmetic_op,
2524
comp_method_OBJECT_ARRAY,
2625
comparison_op,
2726
get_array_op,
2827
logical_op,
2928
)
30-
from pandas.core.ops.common import unpack_zerodim_and_defer
29+
from pandas.core.ops.common import unpack_zerodim_and_defer # noqa:F401
3130
from pandas.core.ops.docstrings import (
3231
_arith_doc_FRAME,
3332
_flex_comp_doc_FRAME,
@@ -300,29 +299,6 @@ def align_method_SERIES(left: "Series", right, align_asobject: bool = False):
300299
return left, right
301300

302301

303-
def arith_method_SERIES(cls, op, special):
304-
"""
305-
Wrapper function for Series arithmetic operations, to avoid
306-
code duplication.
307-
"""
308-
assert special # non-special uses flex_method_SERIES
309-
op_name = _get_op_name(op, special)
310-
311-
@unpack_zerodim_and_defer(op_name)
312-
def wrapper(left, right):
313-
res_name = get_op_result_name(left, right)
314-
left, right = align_method_SERIES(left, right)
315-
316-
lvalues = extract_array(left, extract_numpy=True)
317-
rvalues = extract_array(right, extract_numpy=True)
318-
result = arithmetic_op(lvalues, rvalues, op)
319-
320-
return left._construct_result(result, name=res_name)
321-
322-
wrapper.__name__ = op_name
323-
return wrapper
324-
325-
326302
def flex_method_SERIES(cls, op, special):
327303
assert not special # "special" also means "not flex"
328304
name = _get_op_name(op, special)

pandas/core/ops/methods.py

+53-40
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ def _get_method_wrappers(cls):
4545
# are no longer in __init__
4646
from pandas.core.ops import (
4747
arith_method_FRAME,
48-
arith_method_SERIES,
4948
comp_method_FRAME,
5049
flex_comp_method_FRAME,
5150
flex_method_SERIES,
@@ -55,7 +54,7 @@ def _get_method_wrappers(cls):
5554
# Just Series
5655
arith_flex = flex_method_SERIES
5756
comp_flex = flex_method_SERIES
58-
arith_special = arith_method_SERIES
57+
arith_special = None
5958
comp_special = None
6059
bool_special = None
6160
elif issubclass(cls, ABCDataFrame):
@@ -105,20 +104,19 @@ def f(self, other):
105104
f.__name__ = f"__i{name}__"
106105
return f
107106

108-
new_methods.update(
109-
dict(
110-
__iadd__=_wrap_inplace_method(new_methods["__add__"]),
111-
__isub__=_wrap_inplace_method(new_methods["__sub__"]),
112-
__imul__=_wrap_inplace_method(new_methods["__mul__"]),
113-
__itruediv__=_wrap_inplace_method(new_methods["__truediv__"]),
114-
__ifloordiv__=_wrap_inplace_method(new_methods["__floordiv__"]),
115-
__imod__=_wrap_inplace_method(new_methods["__mod__"]),
116-
__ipow__=_wrap_inplace_method(new_methods["__pow__"]),
117-
)
118-
)
119-
120107
if bool_method is None:
121-
# Series gets bool_method via OpsMixin
108+
# Series gets bool_method, arith_method via OpsMixin
109+
new_methods.update(
110+
dict(
111+
__iadd__=_wrap_inplace_method(cls.__add__),
112+
__isub__=_wrap_inplace_method(cls.__sub__),
113+
__imul__=_wrap_inplace_method(cls.__mul__),
114+
__itruediv__=_wrap_inplace_method(cls.__truediv__),
115+
__ifloordiv__=_wrap_inplace_method(cls.__floordiv__),
116+
__imod__=_wrap_inplace_method(cls.__mod__),
117+
__ipow__=_wrap_inplace_method(cls.__pow__),
118+
)
119+
)
122120
new_methods.update(
123121
dict(
124122
__iand__=_wrap_inplace_method(cls.__and__),
@@ -127,6 +125,17 @@ def f(self, other):
127125
)
128126
)
129127
else:
128+
new_methods.update(
129+
dict(
130+
__iadd__=_wrap_inplace_method(new_methods["__add__"]),
131+
__isub__=_wrap_inplace_method(new_methods["__sub__"]),
132+
__imul__=_wrap_inplace_method(new_methods["__mul__"]),
133+
__itruediv__=_wrap_inplace_method(new_methods["__truediv__"]),
134+
__ifloordiv__=_wrap_inplace_method(new_methods["__floordiv__"]),
135+
__imod__=_wrap_inplace_method(new_methods["__mod__"]),
136+
__ipow__=_wrap_inplace_method(new_methods["__pow__"]),
137+
)
138+
)
130139
new_methods.update(
131140
dict(
132141
__iand__=_wrap_inplace_method(new_methods["__and__"]),
@@ -172,30 +181,34 @@ def _create_methods(cls, arith_method, comp_method, bool_method, special):
172181
have_divmod = issubclass(cls, ABCSeries)
173182
# divmod is available for Series
174183

175-
new_methods = dict(
176-
add=arith_method(cls, operator.add, special),
177-
radd=arith_method(cls, radd, special),
178-
sub=arith_method(cls, operator.sub, special),
179-
mul=arith_method(cls, operator.mul, special),
180-
truediv=arith_method(cls, operator.truediv, special),
181-
floordiv=arith_method(cls, operator.floordiv, special),
182-
mod=arith_method(cls, operator.mod, special),
183-
pow=arith_method(cls, operator.pow, special),
184-
# not entirely sure why this is necessary, but previously was included
185-
# so it's here to maintain compatibility
186-
rmul=arith_method(cls, rmul, special),
187-
rsub=arith_method(cls, rsub, special),
188-
rtruediv=arith_method(cls, rtruediv, special),
189-
rfloordiv=arith_method(cls, rfloordiv, special),
190-
rpow=arith_method(cls, rpow, special),
191-
rmod=arith_method(cls, rmod, special),
192-
)
193-
new_methods["div"] = new_methods["truediv"]
194-
new_methods["rdiv"] = new_methods["rtruediv"]
195-
if have_divmod:
196-
# divmod doesn't have an op that is supported by numexpr
197-
new_methods["divmod"] = arith_method(cls, divmod, special)
198-
new_methods["rdivmod"] = arith_method(cls, rdivmod, special)
184+
new_methods = {}
185+
if arith_method is not None:
186+
new_methods.update(
187+
dict(
188+
add=arith_method(cls, operator.add, special),
189+
radd=arith_method(cls, radd, special),
190+
sub=arith_method(cls, operator.sub, special),
191+
mul=arith_method(cls, operator.mul, special),
192+
truediv=arith_method(cls, operator.truediv, special),
193+
floordiv=arith_method(cls, operator.floordiv, special),
194+
mod=arith_method(cls, operator.mod, special),
195+
pow=arith_method(cls, operator.pow, special),
196+
# not entirely sure why this is necessary, but previously was included
197+
# so it's here to maintain compatibility
198+
rmul=arith_method(cls, rmul, special),
199+
rsub=arith_method(cls, rsub, special),
200+
rtruediv=arith_method(cls, rtruediv, special),
201+
rfloordiv=arith_method(cls, rfloordiv, special),
202+
rpow=arith_method(cls, rpow, special),
203+
rmod=arith_method(cls, rmod, special),
204+
)
205+
)
206+
new_methods["div"] = new_methods["truediv"]
207+
new_methods["rdiv"] = new_methods["rtruediv"]
208+
if have_divmod:
209+
# divmod doesn't have an op that is supported by numexpr
210+
new_methods["divmod"] = arith_method(cls, divmod, special)
211+
new_methods["rdivmod"] = arith_method(cls, rdivmod, special)
199212

200213
if comp_method is not None:
201214
# Series already has this pinned
@@ -210,7 +223,7 @@ def _create_methods(cls, arith_method, comp_method, bool_method, special):
210223
)
211224
)
212225

213-
if bool_method:
226+
if bool_method is not None:
214227
new_methods.update(
215228
dict(
216229
and_=bool_method(cls, operator.and_, special),

0 commit comments

Comments
 (0)