Skip to content

Revert: REF: do length-checks in boilerplate decorator #34137

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 21 commits into from
May 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions pandas/core/arrays/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ def _cat_compare_op(op):

@unpack_zerodim_and_defer(opname)
def func(self, other):
if is_list_like(other) and len(other) != len(self):
# TODO: Could this fail if the categories are listlike objects?
raise ValueError("Lengths must match.")

if not self.ordered:
if opname in ["__lt__", "__gt__", "__le__", "__ge__"]:
raise TypeError(
Expand Down
6 changes: 6 additions & 0 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def _validate_comparison_value(self, other):
elif not is_list_like(other):
raise InvalidComparison(other)

elif len(other) != len(self):
raise ValueError("Lengths must match")

else:
try:
other = self._validate_listlike(other, opname, allow_object=True)
Expand Down Expand Up @@ -1234,6 +1237,9 @@ def _add_timedelta_arraylike(self, other):
"""
# overridden by PeriodArray

if len(self) != len(other):
raise ValueError("cannot add indices of unequal length")

if isinstance(other, np.ndarray):
# ndarray[timedelta64]; wrap in TimedeltaIndex for op
from pandas.core.arrays import TimedeltaArray
Expand Down
3 changes: 3 additions & 0 deletions pandas/core/arrays/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,9 @@ def _assert_tzawareness_compat(self, other):

def _sub_datetime_arraylike(self, other):
"""subtract DatetimeArray/Index or ndarray[datetime64]"""
if len(self) != len(other):
raise ValueError("cannot add indices of unequal length")

if isinstance(other, np.ndarray):
assert is_datetime64_dtype(other)
other = type(self)(other)
Expand Down
4 changes: 4 additions & 0 deletions pandas/core/arrays/integer.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,8 @@ def cmp_method(self, other):
raise NotImplementedError(
"can only perform ops with 1-d structures"
)
if len(self) != len(other):
raise ValueError("Lengths must match to compare")

if other is libmissing.NA:
# numpy does not handle pd.NA well as "other" scalar (it returns
Expand Down Expand Up @@ -620,6 +622,8 @@ def integer_arithmetic_method(self, other):
raise NotImplementedError(
"can only perform ops with 1-d structures"
)
if len(self) != len(other):
raise ValueError("Lengths must match")
if not (is_float_dtype(other) or is_integer_dtype(other)):
raise TypeError("can only perform ops with numeric values")

Expand Down
23 changes: 19 additions & 4 deletions pandas/core/arrays/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,10 @@ def __mul__(self, other):
if not hasattr(other, "dtype"):
# list, tuple
other = np.array(other)
if len(other) != len(self) and not is_timedelta64_dtype(other.dtype):
# Exclude timedelta64 here so we correctly raise TypeError
# for that instead of ValueError
raise ValueError("Cannot multiply with unequal lengths")

if is_object_dtype(other.dtype):
# this multiplication will succeed only if all elements of other
Expand Down Expand Up @@ -514,7 +518,10 @@ def __truediv__(self, other):
# e.g. list, tuple
other = np.array(other)

if is_timedelta64_dtype(other.dtype):
if len(other) != len(self):
raise ValueError("Cannot divide vectors with unequal lengths")

elif is_timedelta64_dtype(other.dtype):
# let numpy handle it
return self._data / other

Expand Down Expand Up @@ -564,7 +571,10 @@ def __rtruediv__(self, other):
# e.g. list, tuple
other = np.array(other)

if is_timedelta64_dtype(other.dtype):
if len(other) != len(self):
raise ValueError("Cannot divide vectors with unequal lengths")

elif is_timedelta64_dtype(other.dtype):
# let numpy handle it
return other / self._data

Expand Down Expand Up @@ -613,8 +623,10 @@ def __floordiv__(self, other):
if not hasattr(other, "dtype"):
# list, tuple
other = np.array(other)
if len(other) != len(self):
raise ValueError("Cannot divide with unequal lengths")

if is_timedelta64_dtype(other.dtype):
elif is_timedelta64_dtype(other.dtype):
other = type(self)(other)

# numpy timedelta64 does not natively support floordiv, so operate
Expand Down Expand Up @@ -666,7 +678,10 @@ def __rfloordiv__(self, other):
# list, tuple
other = np.array(other)

if is_timedelta64_dtype(other.dtype):
if len(other) != len(self):
raise ValueError("Cannot divide with unequal lengths")

elif is_timedelta64_dtype(other.dtype):
other = type(self)(other)

# numpy timedelta64 does not natively support floordiv, so operate
Expand Down
5 changes: 3 additions & 2 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
from pandas.core.indexes.frozen import FrozenList
import pandas.core.missing as missing
from pandas.core.ops import get_op_result_name
from pandas.core.ops.common import unpack_zerodim_and_defer
from pandas.core.ops.invalid import make_invalid_op
from pandas.core.sorting import ensure_key_mapped
from pandas.core.strings import StringMethods
Expand Down Expand Up @@ -109,8 +108,10 @@


def _make_comparison_op(op, cls):
@unpack_zerodim_and_defer(op.__name__)
def cmp_method(self, other):
if isinstance(other, (np.ndarray, Index, ABCSeries, ExtensionArray)):
if other.ndim > 0 and len(self) != len(other):
raise ValueError("Lengths must match to compare")

if is_object_dtype(self.dtype) and isinstance(other, ABCCategorical):
left = type(other)(self._values, dtype=other.dtype)
Expand Down
24 changes: 1 addition & 23 deletions pandas/core/ops/common.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
"""
Boilerplate functions used in defining binary operations.
"""
from collections import UserDict
from functools import wraps
from typing import Callable

import numpy as np

from pandas._libs.lib import is_list_like, item_from_zerodim
from pandas._libs.lib import item_from_zerodim
from pandas._typing import F

from pandas.core.dtypes.generic import ABCDataFrame, ABCIndexClass, ABCSeries
Expand Down Expand Up @@ -65,25 +62,6 @@ def new_method(self, other):

other = item_from_zerodim(other)

if isinstance(self, (ABCSeries, ABCDataFrame)) and isinstance(
other, (ABCSeries, ABCDataFrame)
):
# we dont require length matches
pass
elif is_list_like(other, allow_sets=False) and not isinstance(
other, (dict, UserDict)
):
if len(other) != len(self):
if len(other) == 1 and not hasattr(other, "dtype"):
# i.e. unpack scalar list, but leave e.g. Categorical,
# for which the scalar behavior doesnt match the
# array behavior
other = other[0]
else:
raise ValueError(
"Lengths must match", self.shape, np.shape(other), type(other)
)

return method(self, other)

return new_method
2 changes: 1 addition & 1 deletion pandas/tests/arithmetic/test_datetime64.py
Original file line number Diff line number Diff line change
Expand Up @@ -2206,7 +2206,7 @@ def test_sub_dti_dti(self):
# different length raises ValueError
dti1 = date_range("20130101", periods=3)
dti2 = date_range("20130101", periods=4)
msg = "Lengths must match"
msg = "cannot add indices of unequal length"
with pytest.raises(ValueError, match=msg):
dti1 - dti2

Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/arithmetic/test_numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -649,7 +649,7 @@ def test_mul_datelike_raises(self, numeric_idx):

def test_mul_size_mismatch_raises(self, numeric_idx):
idx = numeric_idx
msg = "Lengths must match"
msg = "operands could not be broadcast together"
with pytest.raises(ValueError, match=msg):
idx * idx[0:3]
with pytest.raises(ValueError, match=msg):
Expand Down
16 changes: 7 additions & 9 deletions pandas/tests/arithmetic/test_timedelta64.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ def test_addition_ops(self):
tm.assert_index_equal(result, expected)

# unequal length
msg = "Lengths must match"
msg = "cannot add indices of unequal length"
with pytest.raises(ValueError, match=msg):
tdi + dti[0:1]
with pytest.raises(ValueError, match=msg):
Expand Down Expand Up @@ -1723,7 +1723,7 @@ def test_tdarr_div_length_mismatch(self, box_with_array):
mismatched = [1, 2, 3, 4]

rng = tm.box_expected(rng, box_with_array)
msg = "Lengths must match|Unable to coerce to Series"
msg = "Cannot divide vectors|Unable to coerce to Series"
for obj in [mismatched, mismatched[:2]]:
# one shorter, one longer
for other in [obj, np.array(obj), pd.Index(obj)]:
Expand Down Expand Up @@ -1905,14 +1905,12 @@ def test_td64arr_mul_tdscalar_invalid(self, box_with_array, scalar_td):
def test_td64arr_mul_too_short_raises(self, box_with_array):
idx = TimedeltaIndex(np.arange(5, dtype="int64"))
idx = tm.box_expected(idx, box_with_array)
msg = "|".join(
[
"Lengths must match", # <- EA, Index, Series
"cannot use operands with types dtype", # <- DataFrame
"Unable to coerce to Series", # <- Series
]
msg = (
"cannot use operands with types dtype|"
"Cannot multiply with unequal lengths|"
"Unable to coerce to Series"
)
with pytest.raises((ValueError, TypeError), match=msg):
with pytest.raises(TypeError, match=msg):
# length check before dtype check
idx * idx[:3]
with pytest.raises(ValueError, match=msg):
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/arrays/boolean/test_logical.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_empty_ok(self, all_logical_operators):
def test_logical_length_mismatch_raises(self, all_logical_operators):
op_name = all_logical_operators
a = pd.array([True, False, None], dtype="boolean")
msg = "Lengths must match"
msg = "Lengths must match to compare"

with pytest.raises(ValueError, match=msg):
getattr(a, op_name)([True, False])
Expand Down
5 changes: 2 additions & 3 deletions pandas/tests/arrays/integer/test_arithmetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,8 @@ def test_error(self, data, all_arithmetic_operators):
result = opa(pd.DataFrame({"A": s}))
assert result is NotImplemented

# msg = r"can only perform ops with 1-d structures"
msg = "Lengths must match"
with pytest.raises(ValueError, match=msg):
msg = r"can only perform ops with 1-d structures"
with pytest.raises(NotImplementedError, match=msg):
opa(np.arange(len(s)).reshape(-1, len(s)))

@pytest.mark.parametrize("zero, negative", [(0, False), (0.0, False), (-0.0, True)])
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/arrays/string_/test_string.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def test_add_2d():
a + b

s = pd.Series(a)
with pytest.raises(ValueError, match="Lengths must match"):
with pytest.raises(ValueError, match="3 != 1"):
s + b


Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/indexes/interval/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ def test_comparison(self):
with pytest.raises(TypeError, match=msg):
self.index > np.arange(2)

msg = "Lengths must match"
msg = "Lengths must match to compare"
with pytest.raises(ValueError, match=msg):
self.index > np.arange(3)

Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/indexes/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2423,6 +2423,16 @@ def test_index_repr_bool_nan(self):
out2 = "Index([True, False, nan], dtype='object')"
assert out2 == exp2

@pytest.mark.filterwarnings("ignore:elementwise comparison failed:FutureWarning")
def test_index_with_tuple_bool(self):
# GH34123
# TODO: remove tupleize_cols=False once correct behaviour is restored
# TODO: also this op right now produces FutureWarning from numpy
idx = Index([("a", "b"), ("b", "c"), ("c", "a")], tupleize_cols=False)
result = idx == ("c", "a",)
expected = np.array([False, False, True])
tm.assert_numpy_array_equal(result, expected)


class TestIndexUtils:
@pytest.mark.parametrize(
Expand Down