From d428531c35e168f05f6f0f24ef74db9631e1c866 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 28 Nov 2020 15:41:43 -0800 Subject: [PATCH 1/5] DEPR: ExtensionOpsMixin --- pandas/core/arrays/base.py | 16 ++++++++++++++++ pandas/tests/arrays/test_deprecations.py | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 pandas/tests/arrays/test_deprecations.py diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 448025e05422d..7efe69452f0ec 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -21,6 +21,7 @@ Union, cast, ) +import warnings import numpy as np @@ -1219,6 +1220,21 @@ class ExtensionOpsMixin: with NumPy arrays. """ + def __init_subclass__(cls, /, **kwargs): + # We use __init_subclass__ to handle deprecations + super().__init_subclass__(**kwargs) + + if cls.__name__ != "ExtensionScalarOpsMixin": + # We only want to warn for user-defined subclasses, + # and cannot reference ExtensionScalarOpsMixin directly at this point. + warnings.warn( + "ExtensionOpsMixin and ExtensionScalarOpsMixin are deprecated " + "and will be removed in a future version. Use " + "pd.core.arraylike.OpsMixin instead.", + FutureWarning, + stacklevel=2, + ) + @classmethod def _create_arithmetic_method(cls, op): raise AbstractMethodError(cls) diff --git a/pandas/tests/arrays/test_deprecations.py b/pandas/tests/arrays/test_deprecations.py new file mode 100644 index 0000000000000..7e80072e8794f --- /dev/null +++ b/pandas/tests/arrays/test_deprecations.py @@ -0,0 +1,19 @@ +import pandas._testing as tm +from pandas.core.arrays import ( + ExtensionArray, + ExtensionOpsMixin, + ExtensionScalarOpsMixin, +) + + +def test_extension_ops_mixin_deprecated(): + # GH#37080 deprecated in favor of OpsMixin + with tm.assert_produces_warning(FutureWarning): + + class MySubclass(ExtensionOpsMixin, ExtensionArray): + pass + + with tm.assert_produces_warning(FutureWarning): + + class MyOtherSubclass(ExtensionScalarOpsMixin, ExtensionArray): + pass From de3f3944b91118df8eb6cd7f7f1ae1b6a226bf36 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 28 Nov 2020 15:42:58 -0800 Subject: [PATCH 2/5] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 6aff4f4bd41e2..eafd77376ac0b 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -487,6 +487,7 @@ Deprecations - Deprecated :meth:`Index.asi8` for :class:`Index` subclasses other than :class:`.DatetimeIndex`, :class:`.TimedeltaIndex`, and :class:`PeriodIndex` (:issue:`37877`) - The ``inplace`` parameter of :meth:`Categorical.remove_unused_categories` is deprecated and will be removed in a future version (:issue:`37643`) - 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`) +- :class:`ExtensionOpsMixin` and :class:`ExtensionScalarOpsMixin` are deprecated and will be removed in a future version. Use ``pd.core.arraylike.OpsMixin`` instead (:issue:`37080`) .. --------------------------------------------------------------------------- From b3a4e24798f3c223deaaf94c9728ead906e85bfb Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 28 Nov 2020 18:10:09 -0800 Subject: [PATCH 3/5] py37 compat --- pandas/core/arrays/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 7efe69452f0ec..52833e8959e70 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -1220,7 +1220,7 @@ class ExtensionOpsMixin: with NumPy arrays. """ - def __init_subclass__(cls, /, **kwargs): + def __init_subclass__(cls, **kwargs): # We use __init_subclass__ to handle deprecations super().__init_subclass__(**kwargs) From d71acec989cf4f5c131158435d328d4fb5d675cb Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 28 Nov 2020 18:31:55 -0800 Subject: [PATCH 4/5] mypy fixup --- pandas/core/arrays/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 52833e8959e70..32ee897d0a49e 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -1222,7 +1222,7 @@ class ExtensionOpsMixin: def __init_subclass__(cls, **kwargs): # We use __init_subclass__ to handle deprecations - super().__init_subclass__(**kwargs) + super().__init_subclass__() if cls.__name__ != "ExtensionScalarOpsMixin": # We only want to warn for user-defined subclasses, From 631993724cd43b4113fa9df2c27b7216ae643a1e Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 29 Nov 2020 08:01:04 -0800 Subject: [PATCH 5/5] remove last usage of ExtensionScalarOpsMixin --- pandas/tests/extension/decimal/array.py | 44 ++++++++++++++++--- .../tests/extension/decimal/test_decimal.py | 7 +-- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/pandas/tests/extension/decimal/array.py b/pandas/tests/extension/decimal/array.py index 9ede9c7fbd0fd..8000584cff8c5 100644 --- a/pandas/tests/extension/decimal/array.py +++ b/pandas/tests/extension/decimal/array.py @@ -7,12 +7,13 @@ import numpy as np from pandas.core.dtypes.base import ExtensionDtype +from pandas.core.dtypes.cast import maybe_cast_to_extension_array from pandas.core.dtypes.common import is_dtype_equal, is_list_like, pandas_dtype import pandas as pd from pandas.api.extensions import no_default, register_extension_dtype from pandas.core.arraylike import OpsMixin -from pandas.core.arrays import ExtensionArray, ExtensionScalarOpsMixin +from pandas.core.arrays import ExtensionArray from pandas.core.indexers import check_array_indexer @@ -45,7 +46,7 @@ def _is_numeric(self) -> bool: return True -class DecimalArray(OpsMixin, ExtensionScalarOpsMixin, ExtensionArray): +class DecimalArray(OpsMixin, ExtensionArray): __array_priority__ = 1000 def __init__(self, values, dtype=None, copy=False, context=None): @@ -217,6 +218,42 @@ def convert_values(param): return np.asarray(res, dtype=bool) + _do_coerce = True # overriden in DecimalArrayWithoutCoercion + + def _arith_method(self, other, op): + def convert_values(param): + if isinstance(param, ExtensionArray) or is_list_like(param): + ovalues = param + else: # Assume its an object + ovalues = [param] * len(self) + return ovalues + + lvalues = self + rvalues = convert_values(other) + + # If the operator is not defined for the underlying objects, + # a TypeError should be raised + res = [op(a, b) for (a, b) in zip(lvalues, rvalues)] + + def _maybe_convert(arr): + if self._do_coerce: + # https://github.com/pandas-dev/pandas/issues/22850 + # We catch all regular exceptions here, and fall back + # to an ndarray. + res = maybe_cast_to_extension_array(type(self), arr) + if not isinstance(res, type(self)): + # exception raised in _from_sequence; ensure we have ndarray + res = np.asarray(arr) + else: + res = np.asarray(arr) + return res + + if op.__name__ in {"divmod", "rdivmod"}: + a, b = zip(*res) + return _maybe_convert(a), _maybe_convert(b) + + return _maybe_convert(res) + def to_decimal(values, context=None): return DecimalArray([decimal.Decimal(x) for x in values], context=context) @@ -224,6 +261,3 @@ def to_decimal(values, context=None): def make_data(): return [decimal.Decimal(random.random()) for _ in range(100)] - - -DecimalArray._add_arithmetic_ops() diff --git a/pandas/tests/extension/decimal/test_decimal.py b/pandas/tests/extension/decimal/test_decimal.py index 233b658d29782..c3e84f75ebe68 100644 --- a/pandas/tests/extension/decimal/test_decimal.py +++ b/pandas/tests/extension/decimal/test_decimal.py @@ -335,12 +335,7 @@ def _from_sequence(cls, scalars, dtype=None, copy=False): class DecimalArrayWithoutCoercion(DecimalArrayWithoutFromSequence): - @classmethod - def _create_arithmetic_method(cls, op): - return cls._create_method(op, coerce_to_dtype=False) - - -DecimalArrayWithoutCoercion._add_arithmetic_ops() + _do_coerce = False def test_combine_from_sequence_raises():