Skip to content

Commit f65f0d3

Browse files
authored
DEPR: ExtensionOpsMixin -> OpsMixin (#38142)
1 parent 4a35f2d commit f65f0d3

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed

doc/source/whatsnew/v1.2.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ Deprecations
492492
- Deprecated :meth:`Index.asi8` for :class:`Index` subclasses other than :class:`.DatetimeIndex`, :class:`.TimedeltaIndex`, and :class:`PeriodIndex` (:issue:`37877`)
493493
- The ``inplace`` parameter of :meth:`Categorical.remove_unused_categories` is deprecated and will be removed in a future version (:issue:`37643`)
494494
- The ``null_counts`` parameter of :meth:`DataFrame.info` is deprecated and replaced by ``show_counts``. It will be removed in a future version (:issue:`37999`)
495+
- :class:`ExtensionOpsMixin` and :class:`ExtensionScalarOpsMixin` are deprecated and will be removed in a future version. Use ``pd.core.arraylike.OpsMixin`` instead (:issue:`37080`)
495496

496497
.. ---------------------------------------------------------------------------
497498

pandas/core/arrays/base.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
Union,
2222
cast,
2323
)
24+
import warnings
2425

2526
import numpy as np
2627

@@ -1237,6 +1238,21 @@ class ExtensionOpsMixin:
12371238
with NumPy arrays.
12381239
"""
12391240

1241+
def __init_subclass__(cls, **kwargs):
1242+
# We use __init_subclass__ to handle deprecations
1243+
super().__init_subclass__()
1244+
1245+
if cls.__name__ != "ExtensionScalarOpsMixin":
1246+
# We only want to warn for user-defined subclasses,
1247+
# and cannot reference ExtensionScalarOpsMixin directly at this point.
1248+
warnings.warn(
1249+
"ExtensionOpsMixin and ExtensionScalarOpsMixin are deprecated "
1250+
"and will be removed in a future version. Use "
1251+
"pd.core.arraylike.OpsMixin instead.",
1252+
FutureWarning,
1253+
stacklevel=2,
1254+
)
1255+
12401256
@classmethod
12411257
def _create_arithmetic_method(cls, op):
12421258
raise AbstractMethodError(cls)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import pandas._testing as tm
2+
from pandas.core.arrays import (
3+
ExtensionArray,
4+
ExtensionOpsMixin,
5+
ExtensionScalarOpsMixin,
6+
)
7+
8+
9+
def test_extension_ops_mixin_deprecated():
10+
# GH#37080 deprecated in favor of OpsMixin
11+
with tm.assert_produces_warning(FutureWarning):
12+
13+
class MySubclass(ExtensionOpsMixin, ExtensionArray):
14+
pass
15+
16+
with tm.assert_produces_warning(FutureWarning):
17+
18+
class MyOtherSubclass(ExtensionScalarOpsMixin, ExtensionArray):
19+
pass

pandas/tests/extension/decimal/array.py

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
import numpy as np
88

99
from pandas.core.dtypes.base import ExtensionDtype
10+
from pandas.core.dtypes.cast import maybe_cast_to_extension_array
1011
from pandas.core.dtypes.common import is_dtype_equal, is_list_like, pandas_dtype
1112

1213
import pandas as pd
1314
from pandas.api.extensions import no_default, register_extension_dtype
1415
from pandas.core.arraylike import OpsMixin
15-
from pandas.core.arrays import ExtensionArray, ExtensionScalarOpsMixin
16+
from pandas.core.arrays import ExtensionArray
1617
from pandas.core.indexers import check_array_indexer
1718

1819

@@ -45,7 +46,7 @@ def _is_numeric(self) -> bool:
4546
return True
4647

4748

48-
class DecimalArray(OpsMixin, ExtensionScalarOpsMixin, ExtensionArray):
49+
class DecimalArray(OpsMixin, ExtensionArray):
4950
__array_priority__ = 1000
5051

5152
def __init__(self, values, dtype=None, copy=False, context=None):
@@ -225,13 +226,46 @@ def convert_values(param):
225226

226227
return np.asarray(res, dtype=bool)
227228

229+
_do_coerce = True # overriden in DecimalArrayWithoutCoercion
230+
231+
def _arith_method(self, other, op):
232+
def convert_values(param):
233+
if isinstance(param, ExtensionArray) or is_list_like(param):
234+
ovalues = param
235+
else: # Assume its an object
236+
ovalues = [param] * len(self)
237+
return ovalues
238+
239+
lvalues = self
240+
rvalues = convert_values(other)
241+
242+
# If the operator is not defined for the underlying objects,
243+
# a TypeError should be raised
244+
res = [op(a, b) for (a, b) in zip(lvalues, rvalues)]
245+
246+
def _maybe_convert(arr):
247+
if self._do_coerce:
248+
# https://github.com/pandas-dev/pandas/issues/22850
249+
# We catch all regular exceptions here, and fall back
250+
# to an ndarray.
251+
res = maybe_cast_to_extension_array(type(self), arr)
252+
if not isinstance(res, type(self)):
253+
# exception raised in _from_sequence; ensure we have ndarray
254+
res = np.asarray(arr)
255+
else:
256+
res = np.asarray(arr)
257+
return res
258+
259+
if op.__name__ in {"divmod", "rdivmod"}:
260+
a, b = zip(*res)
261+
return _maybe_convert(a), _maybe_convert(b)
262+
263+
return _maybe_convert(res)
264+
228265

229266
def to_decimal(values, context=None):
230267
return DecimalArray([decimal.Decimal(x) for x in values], context=context)
231268

232269

233270
def make_data():
234271
return [decimal.Decimal(random.random()) for _ in range(100)]
235-
236-
237-
DecimalArray._add_arithmetic_ops()

pandas/tests/extension/decimal/test_decimal.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -335,12 +335,7 @@ def _from_sequence(cls, scalars, dtype=None, copy=False):
335335

336336

337337
class DecimalArrayWithoutCoercion(DecimalArrayWithoutFromSequence):
338-
@classmethod
339-
def _create_arithmetic_method(cls, op):
340-
return cls._create_method(op, coerce_to_dtype=False)
341-
342-
343-
DecimalArrayWithoutCoercion._add_arithmetic_ops()
338+
_do_coerce = False
344339

345340

346341
def test_combine_from_sequence_raises():

0 commit comments

Comments
 (0)