Skip to content

REF: use check_setitem_lengths in DTA.__setitem__ #36339

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
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
24 changes: 5 additions & 19 deletions pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime, timedelta
import operator
from typing import Any, Callable, Optional, Sequence, Tuple, Type, TypeVar, Union, cast
from typing import Any, Callable, Optional, Sequence, Tuple, Type, TypeVar, Union
import warnings

import numpy as np
Expand Down Expand Up @@ -58,7 +58,7 @@
from pandas.core.arrays.base import ExtensionOpsMixin
import pandas.core.common as com
from pandas.core.construction import array, extract_array
from pandas.core.indexers import check_array_indexer
from pandas.core.indexers import check_array_indexer, check_setitem_lengths
from pandas.core.ops.common import unpack_zerodim_and_defer
from pandas.core.ops.invalid import invalid_comparison, make_invalid_op

Expand Down Expand Up @@ -605,23 +605,9 @@ def __setitem__(
# to a period in from_sequence). For DatetimeArray, it's Timestamp...
# I don't know if mypy can do that, possibly with Generics.
# https://mypy.readthedocs.io/en/latest/generics.html
if is_list_like(value):
is_slice = isinstance(key, slice)

if lib.is_scalar(key):
raise ValueError("setting an array element with a sequence.")

if not is_slice:
key = cast(Sequence, key)
if len(key) != len(value) and not com.is_bool_indexer(key):
msg = (
f"shape mismatch: value array of length '{len(key)}' "
"does not match indexing result of length "
f"'{len(value)}'."
)
raise ValueError(msg)
elif not len(key):
return
no_op = check_setitem_lengths(key, value, self)
if no_op:
return

value = self._validate_setitem_value(value)
key = check_array_indexer(self, key)
Expand Down
42 changes: 27 additions & 15 deletions pandas/core/indexers.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def is_empty_indexer(indexer, arr_value: np.ndarray) -> bool:
# Indexer Validation


def check_setitem_lengths(indexer, value, values) -> None:
def check_setitem_lengths(indexer, value, values) -> bool:
"""
Validate that value and indexer are the same length.
Expand All @@ -133,34 +133,46 @@ def check_setitem_lengths(indexer, value, values) -> None:
Returns
-------
None
bool
Whether this is an empty listlike setting which is a no-op.
Raises
------
ValueError
When the indexer is an ndarray or list and the lengths don't match.
"""
# boolean with truth values == len of the value is ok too
no_op = False

if isinstance(indexer, (np.ndarray, list)):
if is_list_like(value) and len(indexer) != len(value):
if not (
isinstance(indexer, np.ndarray)
and indexer.dtype == np.bool_
and len(indexer[indexer]) == len(value)
):
raise ValueError(
"cannot set using a list-like indexer "
"with a different length than the value"
)
# We can ignore other listlikes becasue they are either
# a) not necessarily 1-D indexers, e.g. tuple
# b) boolean indexers e.g. BoolArray
if is_list_like(value):
if len(indexer) != len(value):
# boolean with truth values == len of the value is ok too
if not (
isinstance(indexer, np.ndarray)
and indexer.dtype == np.bool_
and len(indexer[indexer]) == len(value)
):
raise ValueError(
"cannot set using a list-like indexer "
"with a different length than the value"
)
if not len(indexer):
no_op = True

elif isinstance(indexer, slice):
# slice
if is_list_like(value) and len(values):
if is_list_like(value):
if len(value) != length_of_indexer(indexer, values):
raise ValueError(
"cannot set using a slice indexer with a "
"different length than the value"
)
if not len(value):
no_op = True

return no_op


def validate_indices(indices: np.ndarray, n: int) -> None:
Expand Down
10 changes: 10 additions & 0 deletions pandas/tests/arrays/test_datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,16 @@ def test_setitem_raises(self):
with pytest.raises(TypeError, match="'value' should be a.* 'object'"):
arr[0] = object()

msg = "cannot set using a list-like indexer with a different length"
with pytest.raises(ValueError, match=msg):
# GH#36339
arr[[]] = [arr[1]]

msg = "cannot set using a slice indexer with a different length than"
with pytest.raises(ValueError, match=msg):
# GH#36339
arr[1:1] = arr[:3]

@pytest.mark.parametrize("box", [list, np.array, pd.Index, pd.Series])
def test_setitem_numeric_raises(self, arr1d, box):
# We dont case e.g. int64 to our own dtype for setitem
Expand Down