diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 6cb597ba75852..7447d593a7ff0 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -1206,7 +1206,7 @@ def _maybe_convert(arr): return _maybe_convert(res) - op_name = ops._get_op_name(op, True) + op_name = f"__{op.__name__}__" return set_function_name(_binop, op_name, cls) @classmethod diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index df58593bc930c..d9a93ac7ba289 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -3286,7 +3286,7 @@ def reindex(self, target, method=None, level=None, limit=None, tolerance=None): preserve_names = not hasattr(target, "name") # GH7774: preserve dtype/tz if target is empty and not an Index. - target = _ensure_has_len(target) # target may be an iterator + target = ensure_has_len(target) # target may be an iterator if not isinstance(target, Index) and len(target) == 0: if isinstance(self, ABCRangeIndex): @@ -5573,7 +5573,7 @@ def ensure_index(index_like, copy: bool = False): return Index(index_like) -def _ensure_has_len(seq): +def ensure_has_len(seq): """ If seq is an iterator, put its values into a list. """ diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 7aa1456846612..9126a7a3309d2 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -2288,7 +2288,7 @@ def reindex(self, target, method=None, level=None, limit=None, tolerance=None): # GH7774: preserve dtype/tz if target is empty and not an Index. # target may be an iterator - target = ibase._ensure_has_len(target) + target = ibase.ensure_has_len(target) if len(target) == 0 and not isinstance(target, Index): idx = self.levels[level] attrs = idx._get_attributes_dict() diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index d6ba9d763366b..c14c4a311d66c 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -10,7 +10,7 @@ from pandas._libs import lib from pandas._libs.ops_dispatch import maybe_dispatch_ufunc_to_dunder_op # noqa:F401 -from pandas._typing import ArrayLike, Level +from pandas._typing import Level from pandas.util._decorators import Appender from pandas.core.dtypes.common import is_list_like @@ -224,7 +224,7 @@ def _get_opstr(op): }[op] -def _get_op_name(op, special): +def _get_op_name(op, special: bool) -> str: """ Find the name to attach to this method according to conventions for special and non-special methods. @@ -385,42 +385,6 @@ def _align_method_SERIES(left, right, align_asobject=False): return left, right -def _construct_result( - left: ABCSeries, result: ArrayLike, index: ABCIndexClass, name, -): - """ - Construct an appropriately-labelled Series from the result of an op. - - Parameters - ---------- - left : Series - result : ndarray or ExtensionArray - index : Index - name : object - - Returns - ------- - Series - In the case of __divmod__ or __rdivmod__, a 2-tuple of Series. - """ - if isinstance(result, tuple): - # produced by divmod or rdivmod - return ( - _construct_result(left, result[0], index=index, name=name), - _construct_result(left, result[1], index=index, name=name), - ) - - # We do not pass dtype to ensure that the Series constructor - # does inference in the case where `result` has object-dtype. - out = left._constructor(result, index=index) - out = out.__finalize__(left) - - # Set the result's name after __finalize__ is called because __finalize__ - # would set it back to self.name - out.name = name - return out - - def _arith_method_SERIES(cls, op, special): """ Wrapper function for Series arithmetic operations, to avoid @@ -439,7 +403,7 @@ def wrapper(left, right): rvalues = extract_array(right, extract_numpy=True) result = arithmetic_op(lvalues, rvalues, op, str_rep) - return _construct_result(left, result, index=left.index, name=res_name) + return left._construct_result(result, name=res_name) wrapper.__name__ = op_name return wrapper @@ -466,7 +430,7 @@ def wrapper(self, other): res_values = comparison_op(lvalues, rvalues, op, str_rep) - return _construct_result(self, res_values, index=self.index, name=res_name) + return self._construct_result(res_values, name=res_name) wrapper.__name__ = op_name return wrapper @@ -488,7 +452,7 @@ def wrapper(self, other): rvalues = extract_array(other, extract_numpy=True) res_values = logical_op(lvalues, rvalues, op) - return _construct_result(self, res_values, index=self.index, name=res_name) + return self._construct_result(res_values, name=res_name) wrapper.__name__ = op_name return wrapper diff --git a/pandas/core/series.py b/pandas/core/series.py index 2f4ca61a402dc..78368ee9f6851 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -14,6 +14,7 @@ Optional, Tuple, Type, + Union, ) import warnings @@ -22,7 +23,7 @@ from pandas._config import get_option from pandas._libs import lib, properties, reshape, tslibs -from pandas._typing import Axis, DtypeObj, Label +from pandas._typing import ArrayLike, Axis, DtypeObj, Label from pandas.compat.numpy import function as nv from pandas.util._decorators import Appender, Substitution, doc from pandas.util._validators import validate_bool_kwarg, validate_percentile @@ -2623,12 +2624,10 @@ def _binop(self, other, func, level=None, fill_value=None): if not isinstance(other, Series): raise AssertionError("Other operand must be Series") - new_index = self.index this = self if not self.index.equals(other.index): this, other = self.align(other, level=level, join="outer", copy=False) - new_index = this.index this_vals, other_vals = ops.fill_binop(this.values, other.values, fill_value) @@ -2636,9 +2635,46 @@ def _binop(self, other, func, level=None, fill_value=None): result = func(this_vals, other_vals) name = ops.get_op_result_name(self, other) - ret = ops._construct_result(self, result, new_index, name) + ret = this._construct_result(result, name) return ret + def _construct_result( + self, result: Union[ArrayLike, Tuple[ArrayLike, ArrayLike]], name: Label + ) -> Union["Series", Tuple["Series", "Series"]]: + """ + Construct an appropriately-labelled Series from the result of an op. + + Parameters + ---------- + result : ndarray or ExtensionArray + name : Label + + Returns + ------- + Series + In the case of __divmod__ or __rdivmod__, a 2-tuple of Series. + """ + if isinstance(result, tuple): + # produced by divmod or rdivmod + + res1 = self._construct_result(result[0], name=name) + res2 = self._construct_result(result[1], name=name) + + # GH#33427 assertions to keep mypy happy + assert isinstance(res1, Series) + assert isinstance(res2, Series) + return (res1, res2) + + # We do not pass dtype to ensure that the Series constructor + # does inference in the case where `result` has object-dtype. + out = self._constructor(result, index=self.index) + out = out.__finalize__(self) + + # Set the result's name after __finalize__ is called because __finalize__ + # would set it back to self.name + out.name = name + return out + def combine(self, other, func, fill_value=None) -> "Series": """ Combine the Series with a Series or scalar according to `func`.