Skip to content

Commit e4b0670

Browse files
jbrockmendelKevin D Smith
authored and
Kevin D Smith
committed
REF: use check_setitem_lengths in DTA.__setitem__ (pandas-dev#36339)
1 parent cca7b43 commit e4b0670

File tree

3 files changed

+42
-34
lines changed

3 files changed

+42
-34
lines changed

pandas/core/arrays/datetimelike.py

+5-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime, timedelta
22
import operator
3-
from typing import Any, Callable, Optional, Sequence, Tuple, Type, TypeVar, Union, cast
3+
from typing import Any, Callable, Optional, Sequence, Tuple, Type, TypeVar, Union
44
import warnings
55

66
import numpy as np
@@ -58,7 +58,7 @@
5858
from pandas.core.arrays.base import ExtensionOpsMixin
5959
import pandas.core.common as com
6060
from pandas.core.construction import array, extract_array
61-
from pandas.core.indexers import check_array_indexer
61+
from pandas.core.indexers import check_array_indexer, check_setitem_lengths
6262
from pandas.core.ops.common import unpack_zerodim_and_defer
6363
from pandas.core.ops.invalid import invalid_comparison, make_invalid_op
6464

@@ -605,23 +605,9 @@ def __setitem__(
605605
# to a period in from_sequence). For DatetimeArray, it's Timestamp...
606606
# I don't know if mypy can do that, possibly with Generics.
607607
# https://mypy.readthedocs.io/en/latest/generics.html
608-
if is_list_like(value):
609-
is_slice = isinstance(key, slice)
610-
611-
if lib.is_scalar(key):
612-
raise ValueError("setting an array element with a sequence.")
613-
614-
if not is_slice:
615-
key = cast(Sequence, key)
616-
if len(key) != len(value) and not com.is_bool_indexer(key):
617-
msg = (
618-
f"shape mismatch: value array of length '{len(key)}' "
619-
"does not match indexing result of length "
620-
f"'{len(value)}'."
621-
)
622-
raise ValueError(msg)
623-
elif not len(key):
624-
return
608+
no_op = check_setitem_lengths(key, value, self)
609+
if no_op:
610+
return
625611

626612
value = self._validate_setitem_value(value)
627613
key = check_array_indexer(self, key)

pandas/core/indexers.py

+27-15
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def is_empty_indexer(indexer, arr_value: np.ndarray) -> bool:
114114
# Indexer Validation
115115

116116

117-
def check_setitem_lengths(indexer, value, values) -> None:
117+
def check_setitem_lengths(indexer, value, values) -> bool:
118118
"""
119119
Validate that value and indexer are the same length.
120120
@@ -133,34 +133,46 @@ def check_setitem_lengths(indexer, value, values) -> None:
133133
134134
Returns
135135
-------
136-
None
136+
bool
137+
Whether this is an empty listlike setting which is a no-op.
137138
138139
Raises
139140
------
140141
ValueError
141142
When the indexer is an ndarray or list and the lengths don't match.
142143
"""
143-
# boolean with truth values == len of the value is ok too
144+
no_op = False
145+
144146
if isinstance(indexer, (np.ndarray, list)):
145-
if is_list_like(value) and len(indexer) != len(value):
146-
if not (
147-
isinstance(indexer, np.ndarray)
148-
and indexer.dtype == np.bool_
149-
and len(indexer[indexer]) == len(value)
150-
):
151-
raise ValueError(
152-
"cannot set using a list-like indexer "
153-
"with a different length than the value"
154-
)
147+
# We can ignore other listlikes becasue they are either
148+
# a) not necessarily 1-D indexers, e.g. tuple
149+
# b) boolean indexers e.g. BoolArray
150+
if is_list_like(value):
151+
if len(indexer) != len(value):
152+
# boolean with truth values == len of the value is ok too
153+
if not (
154+
isinstance(indexer, np.ndarray)
155+
and indexer.dtype == np.bool_
156+
and len(indexer[indexer]) == len(value)
157+
):
158+
raise ValueError(
159+
"cannot set using a list-like indexer "
160+
"with a different length than the value"
161+
)
162+
if not len(indexer):
163+
no_op = True
155164

156165
elif isinstance(indexer, slice):
157-
# slice
158-
if is_list_like(value) and len(values):
166+
if is_list_like(value):
159167
if len(value) != length_of_indexer(indexer, values):
160168
raise ValueError(
161169
"cannot set using a slice indexer with a "
162170
"different length than the value"
163171
)
172+
if not len(value):
173+
no_op = True
174+
175+
return no_op
164176

165177

166178
def validate_indices(indices: np.ndarray, n: int) -> None:

pandas/tests/arrays/test_datetimelike.py

+10
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,16 @@ def test_setitem_raises(self):
338338
with pytest.raises(TypeError, match="'value' should be a.* 'object'"):
339339
arr[0] = object()
340340

341+
msg = "cannot set using a list-like indexer with a different length"
342+
with pytest.raises(ValueError, match=msg):
343+
# GH#36339
344+
arr[[]] = [arr[1]]
345+
346+
msg = "cannot set using a slice indexer with a different length than"
347+
with pytest.raises(ValueError, match=msg):
348+
# GH#36339
349+
arr[1:1] = arr[:3]
350+
341351
@pytest.mark.parametrize("box", [list, np.array, pd.Index, pd.Series])
342352
def test_setitem_numeric_raises(self, arr1d, box):
343353
# We dont case e.g. int64 to our own dtype for setitem

0 commit comments

Comments
 (0)