From 407f7f4db6a10d0e17b63a37bc6433790336d841 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 3 Aug 2019 16:46:27 -0700 Subject: [PATCH 1/4] separate out invalid.py --- pandas/core/arrays/datetimelike.py | 27 ++++++------- pandas/core/arrays/datetimes.py | 9 +++-- pandas/core/arrays/timedeltas.py | 8 ++-- pandas/core/indexes/base.py | 3 +- pandas/core/ops/__init__.py | 56 +-------------------------- pandas/core/ops/invalid.py | 61 ++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 77 deletions(-) create mode 100644 pandas/core/ops/invalid.py diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 47b138a9e1604..2df21d5b15c4a 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -44,9 +44,10 @@ from pandas.core.dtypes.missing import is_valid_nat_for_dtype, isna from pandas._typing import DatetimeLikeScalar -from pandas.core import missing, nanops, ops +from pandas.core import missing, nanops from pandas.core.algorithms import checked_add_with_arr, take, unique1d, value_counts import pandas.core.common as com +from pandas.core.ops.invalid import make_invalid_op from pandas.tseries import frequencies from pandas.tseries.offsets import DateOffset, Tick @@ -930,18 +931,18 @@ def _is_unique(self): # pow is invalid for all three subclasses; TimedeltaArray will override # the multiplication and division ops - __pow__ = ops.make_invalid_op("__pow__") - __rpow__ = ops.make_invalid_op("__rpow__") - __mul__ = ops.make_invalid_op("__mul__") - __rmul__ = ops.make_invalid_op("__rmul__") - __truediv__ = ops.make_invalid_op("__truediv__") - __rtruediv__ = ops.make_invalid_op("__rtruediv__") - __floordiv__ = ops.make_invalid_op("__floordiv__") - __rfloordiv__ = ops.make_invalid_op("__rfloordiv__") - __mod__ = ops.make_invalid_op("__mod__") - __rmod__ = ops.make_invalid_op("__rmod__") - __divmod__ = ops.make_invalid_op("__divmod__") - __rdivmod__ = ops.make_invalid_op("__rdivmod__") + __pow__ = make_invalid_op("__pow__") + __rpow__ = make_invalid_op("__rpow__") + __mul__ = make_invalid_op("__mul__") + __rmul__ = make_invalid_op("__rmul__") + __truediv__ = make_invalid_op("__truediv__") + __rtruediv__ = make_invalid_op("__rtruediv__") + __floordiv__ = make_invalid_op("__floordiv__") + __rfloordiv__ = make_invalid_op("__rfloordiv__") + __mod__ = make_invalid_op("__mod__") + __rmod__ = make_invalid_op("__rmod__") + __divmod__ = make_invalid_op("__divmod__") + __rdivmod__ = make_invalid_op("__rdivmod__") def _add_datetimelike_scalar(self, other): # Overriden by TimedeltaArray diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 6a4ca0ab4147a..3ca1909977c96 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -53,6 +53,7 @@ from pandas.core.arrays import datetimelike as dtl from pandas.core.arrays._ranges import generate_regular_range import pandas.core.common as com +from pandas.core.ops.invalid import invalid_comparison from pandas.tseries.frequencies import get_period_alias, to_offset from pandas.tseries.offsets import Day, Tick @@ -171,13 +172,13 @@ def wrapper(self, other): other = _to_M8(other, tz=self.tz) except ValueError: # string that cannot be parsed to Timestamp - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) result = op(self.asi8, other.view("i8")) if isna(other): result.fill(nat_result) elif lib.is_scalar(other) or np.ndim(other) == 0: - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) elif len(other) != len(self): raise ValueError("Lengths must match") else: @@ -191,7 +192,7 @@ def wrapper(self, other): ): # Following Timestamp convention, __eq__ is all-False # and __ne__ is all True, others raise TypeError. - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) if is_object_dtype(other): # We have to use _comp_method_OBJECT_ARRAY instead of numpy @@ -204,7 +205,7 @@ def wrapper(self, other): o_mask = isna(other) elif not (is_datetime64_dtype(other) or is_datetime64tz_dtype(other)): # e.g. is_timedelta64_dtype(other) - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) else: self._assert_tzawareness_compat(other) if isinstance(other, (ABCIndexClass, ABCSeries)): diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index dd0b9a79c6dca..af2da8bbbe143 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -41,9 +41,9 @@ ) from pandas.core.dtypes.missing import isna -from pandas.core import ops from pandas.core.algorithms import checked_add_with_arr import pandas.core.common as com +from pandas.core.ops.invalid import invalid_comparison from pandas.tseries.frequencies import to_offset from pandas.tseries.offsets import Tick @@ -90,14 +90,14 @@ def wrapper(self, other): other = Timedelta(other) except ValueError: # failed to parse as timedelta - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) result = op(self.view("i8"), other.value) if isna(other): result.fill(nat_result) elif not is_list_like(other): - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) elif len(other) != len(self): raise ValueError("Lengths must match") @@ -106,7 +106,7 @@ def wrapper(self, other): try: other = type(self)._from_sequence(other)._data except (ValueError, TypeError): - return ops.invalid_comparison(self, other, op) + return invalid_comparison(self, other, op) result = op(self.view("i8"), other.view("i8")) result = com.values_from_object(result) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index ce7b73a92b18a..90b2fd9cfeec0 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -69,7 +69,8 @@ from pandas.core.indexers import maybe_convert_indices from pandas.core.indexes.frozen import FrozenList import pandas.core.missing as missing -from pandas.core.ops import get_op_result_name, make_invalid_op +from pandas.core.ops import get_op_result_name +from pandas.core.ops.invalid import make_invalid_op import pandas.core.sorting as sorting from pandas.core.strings import StringMethods diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index a9d18c194889c..8eef6eee31230 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -57,6 +57,7 @@ _make_flex_doc, _op_descriptions, ) +from .invalid import invalid_comparison from .roperator import ( # noqa:F401 radd, rand_, @@ -175,29 +176,6 @@ def maybe_upcast_for_op(obj): # ----------------------------------------------------------------------------- -def make_invalid_op(name): - """ - Return a binary method that always raises a TypeError. - - Parameters - ---------- - name : str - - Returns - ------- - invalid_op : function - """ - - def invalid_op(self, other=None): - raise TypeError( - "cannot perform {name} with this index type: " - "{typ}".format(name=name, typ=type(self).__name__) - ) - - invalid_op.__name__ = name - return invalid_op - - def _gen_eval_kwargs(name): """ Find the keyword arguments to pass to numexpr for the given operation. @@ -466,38 +444,6 @@ def masked_arith_op(x, y, op): return result -def invalid_comparison(left, right, op): - """ - If a comparison has mismatched types and is not necessarily meaningful, - follow python3 conventions by: - - - returning all-False for equality - - returning all-True for inequality - - raising TypeError otherwise - - Parameters - ---------- - left : array-like - right : scalar, array-like - op : operator.{eq, ne, lt, le, gt} - - Raises - ------ - TypeError : on inequality comparisons - """ - if op is operator.eq: - res_values = np.zeros(left.shape, dtype=bool) - elif op is operator.ne: - res_values = np.ones(left.shape, dtype=bool) - else: - raise TypeError( - "Invalid comparison between dtype={dtype} and {typ}".format( - dtype=left.dtype, typ=type(right).__name__ - ) - ) - return res_values - - # ----------------------------------------------------------------------------- # Dispatch logic diff --git a/pandas/core/ops/invalid.py b/pandas/core/ops/invalid.py new file mode 100644 index 0000000000000..013ff7689b221 --- /dev/null +++ b/pandas/core/ops/invalid.py @@ -0,0 +1,61 @@ +""" +Templates for invalid operations. +""" +import operator + +import numpy as np + + +def invalid_comparison(left, right, op): + """ + If a comparison has mismatched types and is not necessarily meaningful, + follow python3 conventions by: + + - returning all-False for equality + - returning all-True for inequality + - raising TypeError otherwise + + Parameters + ---------- + left : array-like + right : scalar, array-like + op : operator.{eq, ne, lt, le, gt} + + Raises + ------ + TypeError : on inequality comparisons + """ + if op is operator.eq: + res_values = np.zeros(left.shape, dtype=bool) + elif op is operator.ne: + res_values = np.ones(left.shape, dtype=bool) + else: + raise TypeError( + "Invalid comparison between dtype={dtype} and {typ}".format( + dtype=left.dtype, typ=type(right).__name__ + ) + ) + return res_values + + +def make_invalid_op(name: str): + """ + Return a binary method that always raises a TypeError. + + Parameters + ---------- + name : str + + Returns + ------- + invalid_op : function + """ + + def invalid_op(self, other=None): + raise TypeError( + "cannot perform {name} with this index type: " + "{typ}".format(name=name, typ=type(self).__name__) + ) + + invalid_op.__name__ = name + return invalid_op From 4f9050ada0c02f5a872aaee63be1c6e3006f0226 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 3 Aug 2019 16:47:50 -0700 Subject: [PATCH 2/4] whitespace --- pandas/core/ops/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 8eef6eee31230..7c32fcfb06e4a 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -319,7 +319,6 @@ def _get_op_name(op, special): # ----------------------------------------------------------------------------- # Masking NA values and fallbacks for operations numpy does not support - def fill_binop(left, right, fill_value): """ If a non-None fill_value is given, replace null entries in left and right @@ -447,7 +446,6 @@ def masked_arith_op(x, y, op): # ----------------------------------------------------------------------------- # Dispatch logic - def should_series_dispatch(left, right, op): """ Identify cases where a DataFrame operation should dispatch to its @@ -615,7 +613,6 @@ def dispatch_to_extension_op(op, left, right): # Functions that add arithmetic methods to objects, given arithmetic factory # methods - def _get_method_wrappers(cls): """ Find the appropriate operation-wrappers to use when defining flex/special From 7ef33086d85a22dbbff447d92ac2033c5eac580f Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sat, 3 Aug 2019 16:49:35 -0700 Subject: [PATCH 3/4] revert whitespace --- pandas/core/ops/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 7c32fcfb06e4a..8eef6eee31230 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -319,6 +319,7 @@ def _get_op_name(op, special): # ----------------------------------------------------------------------------- # Masking NA values and fallbacks for operations numpy does not support + def fill_binop(left, right, fill_value): """ If a non-None fill_value is given, replace null entries in left and right @@ -446,6 +447,7 @@ def masked_arith_op(x, y, op): # ----------------------------------------------------------------------------- # Dispatch logic + def should_series_dispatch(left, right, op): """ Identify cases where a DataFrame operation should dispatch to its @@ -613,6 +615,7 @@ def dispatch_to_extension_op(op, left, right): # Functions that add arithmetic methods to objects, given arithmetic factory # methods + def _get_method_wrappers(cls): """ Find the appropriate operation-wrappers to use when defining flex/special From d9b2097170aec0a1a5e2606b7c255b23745079e2 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Sun, 4 Aug 2019 14:18:06 -0700 Subject: [PATCH 4/4] absolute imports --- pandas/core/ops/__init__.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 8eef6eee31230..5ad4144b210b3 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -49,16 +49,15 @@ import pandas as pd from pandas._typing import ArrayLike import pandas.core.common as com - -from . import missing -from .docstrings import ( +from pandas.core.ops import missing +from pandas.core.ops.docstrings import ( _arith_doc_FRAME, _flex_comp_doc_FRAME, _make_flex_doc, _op_descriptions, ) -from .invalid import invalid_comparison -from .roperator import ( # noqa:F401 +from pandas.core.ops.invalid import invalid_comparison +from pandas.core.ops.roperator import ( # noqa:F401 radd, rand_, rdiv,