Skip to content

Commit 2f775b0

Browse files
jbrockmendeljreback
authored andcommitted
REF: separate out invalid ops (#27735)
1 parent f9f95c0 commit 2f775b0

File tree

6 files changed

+90
-81
lines changed

6 files changed

+90
-81
lines changed

pandas/core/arrays/datetimelike.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@
4444
from pandas.core.dtypes.missing import is_valid_nat_for_dtype, isna
4545

4646
from pandas._typing import DatetimeLikeScalar
47-
from pandas.core import missing, nanops, ops
47+
from pandas.core import missing, nanops
4848
from pandas.core.algorithms import checked_add_with_arr, take, unique1d, value_counts
4949
import pandas.core.common as com
50+
from pandas.core.ops.invalid import make_invalid_op
5051

5152
from pandas.tseries import frequencies
5253
from pandas.tseries.offsets import DateOffset, Tick
@@ -921,18 +922,18 @@ def _is_unique(self):
921922

922923
# pow is invalid for all three subclasses; TimedeltaArray will override
923924
# the multiplication and division ops
924-
__pow__ = ops.make_invalid_op("__pow__")
925-
__rpow__ = ops.make_invalid_op("__rpow__")
926-
__mul__ = ops.make_invalid_op("__mul__")
927-
__rmul__ = ops.make_invalid_op("__rmul__")
928-
__truediv__ = ops.make_invalid_op("__truediv__")
929-
__rtruediv__ = ops.make_invalid_op("__rtruediv__")
930-
__floordiv__ = ops.make_invalid_op("__floordiv__")
931-
__rfloordiv__ = ops.make_invalid_op("__rfloordiv__")
932-
__mod__ = ops.make_invalid_op("__mod__")
933-
__rmod__ = ops.make_invalid_op("__rmod__")
934-
__divmod__ = ops.make_invalid_op("__divmod__")
935-
__rdivmod__ = ops.make_invalid_op("__rdivmod__")
925+
__pow__ = make_invalid_op("__pow__")
926+
__rpow__ = make_invalid_op("__rpow__")
927+
__mul__ = make_invalid_op("__mul__")
928+
__rmul__ = make_invalid_op("__rmul__")
929+
__truediv__ = make_invalid_op("__truediv__")
930+
__rtruediv__ = make_invalid_op("__rtruediv__")
931+
__floordiv__ = make_invalid_op("__floordiv__")
932+
__rfloordiv__ = make_invalid_op("__rfloordiv__")
933+
__mod__ = make_invalid_op("__mod__")
934+
__rmod__ = make_invalid_op("__rmod__")
935+
__divmod__ = make_invalid_op("__divmod__")
936+
__rdivmod__ = make_invalid_op("__rdivmod__")
936937

937938
def _add_datetimelike_scalar(self, other):
938939
# Overriden by TimedeltaArray

pandas/core/arrays/datetimes.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from pandas.core.arrays import datetimelike as dtl
5454
from pandas.core.arrays._ranges import generate_regular_range
5555
import pandas.core.common as com
56+
from pandas.core.ops.invalid import invalid_comparison
5657

5758
from pandas.tseries.frequencies import get_period_alias, to_offset
5859
from pandas.tseries.offsets import Day, Tick
@@ -171,13 +172,13 @@ def wrapper(self, other):
171172
other = _to_M8(other, tz=self.tz)
172173
except ValueError:
173174
# string that cannot be parsed to Timestamp
174-
return ops.invalid_comparison(self, other, op)
175+
return invalid_comparison(self, other, op)
175176

176177
result = op(self.asi8, other.view("i8"))
177178
if isna(other):
178179
result.fill(nat_result)
179180
elif lib.is_scalar(other) or np.ndim(other) == 0:
180-
return ops.invalid_comparison(self, other, op)
181+
return invalid_comparison(self, other, op)
181182
elif len(other) != len(self):
182183
raise ValueError("Lengths must match")
183184
else:
@@ -191,7 +192,7 @@ def wrapper(self, other):
191192
):
192193
# Following Timestamp convention, __eq__ is all-False
193194
# and __ne__ is all True, others raise TypeError.
194-
return ops.invalid_comparison(self, other, op)
195+
return invalid_comparison(self, other, op)
195196

196197
if is_object_dtype(other):
197198
# We have to use _comp_method_OBJECT_ARRAY instead of numpy
@@ -204,7 +205,7 @@ def wrapper(self, other):
204205
o_mask = isna(other)
205206
elif not (is_datetime64_dtype(other) or is_datetime64tz_dtype(other)):
206207
# e.g. is_timedelta64_dtype(other)
207-
return ops.invalid_comparison(self, other, op)
208+
return invalid_comparison(self, other, op)
208209
else:
209210
self._assert_tzawareness_compat(other)
210211
if isinstance(other, (ABCIndexClass, ABCSeries)):

pandas/core/arrays/timedeltas.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@
4141
)
4242
from pandas.core.dtypes.missing import isna
4343

44-
from pandas.core import ops
4544
from pandas.core.algorithms import checked_add_with_arr
4645
import pandas.core.common as com
46+
from pandas.core.ops.invalid import invalid_comparison
4747

4848
from pandas.tseries.frequencies import to_offset
4949
from pandas.tseries.offsets import Tick
@@ -90,14 +90,14 @@ def wrapper(self, other):
9090
other = Timedelta(other)
9191
except ValueError:
9292
# failed to parse as timedelta
93-
return ops.invalid_comparison(self, other, op)
93+
return invalid_comparison(self, other, op)
9494

9595
result = op(self.view("i8"), other.value)
9696
if isna(other):
9797
result.fill(nat_result)
9898

9999
elif not is_list_like(other):
100-
return ops.invalid_comparison(self, other, op)
100+
return invalid_comparison(self, other, op)
101101

102102
elif len(other) != len(self):
103103
raise ValueError("Lengths must match")
@@ -106,7 +106,7 @@ def wrapper(self, other):
106106
try:
107107
other = type(self)._from_sequence(other)._data
108108
except (ValueError, TypeError):
109-
return ops.invalid_comparison(self, other, op)
109+
return invalid_comparison(self, other, op)
110110

111111
result = op(self.view("i8"), other.view("i8"))
112112
result = com.values_from_object(result)

pandas/core/indexes/base.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070
from pandas.core.indexers import maybe_convert_indices
7171
from pandas.core.indexes.frozen import FrozenList
7272
import pandas.core.missing as missing
73-
from pandas.core.ops import get_op_result_name, make_invalid_op
73+
from pandas.core.ops import get_op_result_name
74+
from pandas.core.ops.invalid import make_invalid_op
7475
import pandas.core.sorting as sorting
7576
from pandas.core.strings import StringMethods
7677

pandas/core/ops/__init__.py

+4-59
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@
4949
import pandas as pd
5050
from pandas._typing import ArrayLike
5151
from pandas.core.construction import extract_array
52-
53-
from . import missing
54-
from .docstrings import (
52+
from pandas.core.ops import missing
53+
from pandas.core.ops.docstrings import (
5554
_arith_doc_FRAME,
5655
_flex_comp_doc_FRAME,
5756
_make_flex_doc,
5857
_op_descriptions,
5958
)
60-
from .roperator import ( # noqa:F401
59+
from pandas.core.ops.invalid import invalid_comparison
60+
from pandas.core.ops.roperator import ( # noqa:F401
6161
radd,
6262
rand_,
6363
rdiv,
@@ -185,29 +185,6 @@ def maybe_upcast_for_op(obj, shape: Tuple[int, ...]):
185185
# -----------------------------------------------------------------------------
186186

187187

188-
def make_invalid_op(name):
189-
"""
190-
Return a binary method that always raises a TypeError.
191-
192-
Parameters
193-
----------
194-
name : str
195-
196-
Returns
197-
-------
198-
invalid_op : function
199-
"""
200-
201-
def invalid_op(self, other=None):
202-
raise TypeError(
203-
"cannot perform {name} with this index type: "
204-
"{typ}".format(name=name, typ=type(self).__name__)
205-
)
206-
207-
invalid_op.__name__ = name
208-
return invalid_op
209-
210-
211188
def _gen_eval_kwargs(name):
212189
"""
213190
Find the keyword arguments to pass to numexpr for the given operation.
@@ -476,38 +453,6 @@ def masked_arith_op(x, y, op):
476453
return result
477454

478455

479-
def invalid_comparison(left, right, op):
480-
"""
481-
If a comparison has mismatched types and is not necessarily meaningful,
482-
follow python3 conventions by:
483-
484-
- returning all-False for equality
485-
- returning all-True for inequality
486-
- raising TypeError otherwise
487-
488-
Parameters
489-
----------
490-
left : array-like
491-
right : scalar, array-like
492-
op : operator.{eq, ne, lt, le, gt}
493-
494-
Raises
495-
------
496-
TypeError : on inequality comparisons
497-
"""
498-
if op is operator.eq:
499-
res_values = np.zeros(left.shape, dtype=bool)
500-
elif op is operator.ne:
501-
res_values = np.ones(left.shape, dtype=bool)
502-
else:
503-
raise TypeError(
504-
"Invalid comparison between dtype={dtype} and {typ}".format(
505-
dtype=left.dtype, typ=type(right).__name__
506-
)
507-
)
508-
return res_values
509-
510-
511456
# -----------------------------------------------------------------------------
512457
# Dispatch logic
513458

pandas/core/ops/invalid.py

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""
2+
Templates for invalid operations.
3+
"""
4+
import operator
5+
6+
import numpy as np
7+
8+
9+
def invalid_comparison(left, right, op):
10+
"""
11+
If a comparison has mismatched types and is not necessarily meaningful,
12+
follow python3 conventions by:
13+
14+
- returning all-False for equality
15+
- returning all-True for inequality
16+
- raising TypeError otherwise
17+
18+
Parameters
19+
----------
20+
left : array-like
21+
right : scalar, array-like
22+
op : operator.{eq, ne, lt, le, gt}
23+
24+
Raises
25+
------
26+
TypeError : on inequality comparisons
27+
"""
28+
if op is operator.eq:
29+
res_values = np.zeros(left.shape, dtype=bool)
30+
elif op is operator.ne:
31+
res_values = np.ones(left.shape, dtype=bool)
32+
else:
33+
raise TypeError(
34+
"Invalid comparison between dtype={dtype} and {typ}".format(
35+
dtype=left.dtype, typ=type(right).__name__
36+
)
37+
)
38+
return res_values
39+
40+
41+
def make_invalid_op(name: str):
42+
"""
43+
Return a binary method that always raises a TypeError.
44+
45+
Parameters
46+
----------
47+
name : str
48+
49+
Returns
50+
-------
51+
invalid_op : function
52+
"""
53+
54+
def invalid_op(self, other=None):
55+
raise TypeError(
56+
"cannot perform {name} with this index type: "
57+
"{typ}".format(name=name, typ=type(self).__name__)
58+
)
59+
60+
invalid_op.__name__ = name
61+
return invalid_op

0 commit comments

Comments
 (0)