Skip to content

REF/TYP: use OpsMixin for logical methods #36964

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions pandas/core/arraylike.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from pandas.errors import AbstractMethodError

from pandas.core.ops import roperator
from pandas.core.ops.common import unpack_zerodim_and_defer


Expand Down Expand Up @@ -41,3 +42,33 @@ def __gt__(self, other):
@unpack_zerodim_and_defer("__ge__")
def __ge__(self, other):
return self._cmp_method(other, operator.ge)

# -------------------------------------------------------------
# Logical Methods

def _logical_method(self, other, op):
raise AbstractMethodError(self)

@unpack_zerodim_and_defer("__and__")
def __and__(self, other):
return self._logical_method(other, operator.and_)

@unpack_zerodim_and_defer("__rand__")
def __rand__(self, other):
return self._logical_method(other, roperator.rand_)

@unpack_zerodim_and_defer("__or__")
def __or__(self, other):
return self._logical_method(other, operator.or_)

@unpack_zerodim_and_defer("__ror__")
def __ror__(self, other):
return self._logical_method(other, roperator.ror_)

@unpack_zerodim_and_defer("__xor__")
def __xor__(self, other):
return self._logical_method(other, operator.xor)

@unpack_zerodim_and_defer("__rxor__")
def __rxor__(self, other):
return self._logical_method(other, roperator.rxor)
27 changes: 2 additions & 25 deletions pandas/core/ops/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def dispatch_to_series(left, right, func, axis: Optional[int] = None):
# Series


def _align_method_SERIES(left: "Series", right, align_asobject: bool = False):
def align_method_SERIES(left: "Series", right, align_asobject: bool = False):
""" align lhs and rhs Series """
# ToDo: Different from align_method_FRAME, list, tuple and ndarray
# are not coerced here
Expand Down Expand Up @@ -311,7 +311,7 @@ def arith_method_SERIES(cls, op, special):
@unpack_zerodim_and_defer(op_name)
def wrapper(left, right):
res_name = get_op_result_name(left, right)
left, right = _align_method_SERIES(left, right)
left, right = align_method_SERIES(left, right)

lvalues = extract_array(left, extract_numpy=True)
rvalues = extract_array(right, extract_numpy=True)
Expand All @@ -323,29 +323,6 @@ def wrapper(left, right):
return wrapper


def bool_method_SERIES(cls, op, special):
"""
Wrapper function for Series arithmetic operations, to avoid
code duplication.
"""
assert special # non-special uses flex_method_SERIES
op_name = _get_op_name(op, special)

@unpack_zerodim_and_defer(op_name)
def wrapper(self, other):
res_name = get_op_result_name(self, other)
self, other = _align_method_SERIES(self, other, align_asobject=True)

lvalues = extract_array(self, extract_numpy=True)
rvalues = extract_array(other, extract_numpy=True)

res_values = logical_op(lvalues, rvalues, op)
return self._construct_result(res_values, name=res_name)

wrapper.__name__ = op_name
return wrapper


def flex_method_SERIES(cls, op, special):
assert not special # "special" also means "not flex"
name = _get_op_name(op, special)
Expand Down
25 changes: 17 additions & 8 deletions pandas/core/ops/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ def _get_method_wrappers(cls):
from pandas.core.ops import (
arith_method_FRAME,
arith_method_SERIES,
bool_method_SERIES,
comp_method_FRAME,
flex_comp_method_FRAME,
flex_method_SERIES,
Expand All @@ -58,7 +57,7 @@ def _get_method_wrappers(cls):
comp_flex = flex_method_SERIES
arith_special = arith_method_SERIES
comp_special = None
bool_special = bool_method_SERIES
bool_special = None
elif issubclass(cls, ABCDataFrame):
arith_flex = arith_method_FRAME
comp_flex = flex_comp_method_FRAME
Expand Down Expand Up @@ -118,13 +117,23 @@ def f(self, other):
)
)

new_methods.update(
dict(
__iand__=_wrap_inplace_method(new_methods["__and__"]),
__ior__=_wrap_inplace_method(new_methods["__or__"]),
__ixor__=_wrap_inplace_method(new_methods["__xor__"]),
if bool_method is None:
# Series gets bool_method via OpsMixin
new_methods.update(
dict(
__iand__=_wrap_inplace_method(cls.__and__),
__ior__=_wrap_inplace_method(cls.__or__),
__ixor__=_wrap_inplace_method(cls.__xor__),
)
)
else:
new_methods.update(
dict(
__iand__=_wrap_inplace_method(new_methods["__and__"]),
__ior__=_wrap_inplace_method(new_methods["__or__"]),
__ixor__=_wrap_inplace_method(new_methods["__xor__"]),
)
)
)

_add_methods(cls, new_methods=new_methods)

Expand Down
10 changes: 10 additions & 0 deletions pandas/core/series.py
Original file line number Diff line number Diff line change
Expand Up @@ -4978,6 +4978,16 @@ def _cmp_method(self, other, op):

return self._construct_result(res_values, name=res_name)

def _logical_method(self, other, op):
res_name = ops.get_op_result_name(self, other)
self, other = ops.align_method_SERIES(self, other, align_asobject=True)

lvalues = extract_array(self, extract_numpy=True)
rvalues = extract_array(other, extract_numpy=True)

res_values = ops.logical_op(lvalues, rvalues, op)
return self._construct_result(res_values, name=res_name)


Series._add_numeric_operations()

Expand Down
13 changes: 4 additions & 9 deletions pandas/tests/extension/arrow/arrays.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
register_extension_dtype,
take,
)
from pandas.core.arraylike import OpsMixin


@register_extension_dtype
Expand Down Expand Up @@ -67,7 +68,7 @@ def construct_array_type(cls) -> Type["ArrowStringArray"]:
return ArrowStringArray


class ArrowExtensionArray(ExtensionArray):
class ArrowExtensionArray(OpsMixin, ExtensionArray):
_data: pa.ChunkedArray

@classmethod
Expand Down Expand Up @@ -109,7 +110,7 @@ def astype(self, dtype, copy=True):
def dtype(self):
return self._dtype

def _boolean_op(self, other, op):
def _logical_method(self, other, op):
if not isinstance(other, type(self)):
raise NotImplementedError()

Expand All @@ -122,13 +123,7 @@ def __eq__(self, other):
if not isinstance(other, type(self)):
return False

return self._boolean_op(other, operator.eq)

def __and__(self, other):
return self._boolean_op(other, operator.and_)

def __or__(self, other):
return self._boolean_op(other, operator.or_)
return self._logical_method(other, operator.eq)

@property
def nbytes(self) -> int:
Expand Down