Skip to content

Commit ebd2049

Browse files
jbrockmendelphofl
authored andcommitted
REF: use try/except pattern in Block.setitem, EABackedBlock.setitem (pandas-dev#45742)
1 parent f44cc6e commit ebd2049

File tree

2 files changed

+51
-31
lines changed

2 files changed

+51
-31
lines changed

pandas/core/arrays/datetimelike.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -389,11 +389,16 @@ def __setitem__( # type: ignore[override]
389389
# to a period in from_sequence). For DatetimeArray, it's Timestamp...
390390
# I don't know if mypy can do that, possibly with Generics.
391391
# https://mypy.readthedocs.io/en/latest/generics.html
392+
392393
no_op = check_setitem_lengths(key, value, self)
394+
395+
# Calling super() before the no_op short-circuit means that we raise
396+
# on invalid 'value' even if this is a no-op, e.g. wrong-dtype empty array.
397+
super().__setitem__(key, value)
398+
393399
if no_op:
394400
return
395401

396-
super().__setitem__(key, value)
397402
self._maybe_clear_freq()
398403

399404
def _maybe_clear_freq(self):

pandas/core/internals/blocks.py

+45-30
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
writers,
2323
)
2424
from pandas._libs.internals import BlockPlacement
25+
from pandas._libs.tslibs import IncompatibleFrequency
2526
from pandas._typing import (
2627
ArrayLike,
2728
DtypeObj,
@@ -105,11 +106,7 @@
105106
ensure_wrapped_if_datetimelike,
106107
extract_array,
107108
)
108-
from pandas.core.indexers import (
109-
check_setitem_lengths,
110-
is_empty_indexer,
111-
is_scalar_indexer,
112-
)
109+
from pandas.core.indexers import check_setitem_lengths
113110
import pandas.core.missing as missing
114111

115112
if TYPE_CHECKING:
@@ -919,32 +916,29 @@ def setitem(self, indexer, value):
919916

920917
value = self._standardize_fill_value(value)
921918

922-
# coerce if block dtype can store value
923-
if not self._can_hold_element(value):
924-
# current dtype cannot store value, coerce to common dtype
925-
return self.coerce_to_target_dtype(value).setitem(indexer, value)
926-
927-
# value must be storable at this moment
928919
values = cast(np.ndarray, self.values)
929920
if self.ndim == 2:
930921
values = values.T
931922

932923
# length checking
933924
check_setitem_lengths(indexer, value, values)
934925

935-
if is_empty_indexer(indexer):
936-
# GH#8669 empty indexers, test_loc_setitem_boolean_mask_allfalse
937-
values[indexer] = value
938-
939-
elif is_scalar_indexer(indexer, self.ndim):
940-
# setting a single element for each dim and with a rhs that could
941-
# be e.g. a list; see GH#6043
942-
values[indexer] = value
943-
926+
value = extract_array(value, extract_numpy=True)
927+
try:
928+
casted = np_can_hold_element(values.dtype, value)
929+
except ValueError:
930+
# current dtype cannot store value, coerce to common dtype
931+
nb = self.coerce_to_target_dtype(value)
932+
return nb.setitem(indexer, value)
944933
else:
945-
value = setitem_datetimelike_compat(values, len(values[indexer]), value)
946-
values[indexer] = value
947-
934+
if self.dtype == _dtype_obj:
935+
# TODO: avoid having to construct values[indexer]
936+
vi = values[indexer]
937+
if lib.is_list_like(vi):
938+
# checking lib.is_scalar here fails on
939+
# test_iloc_setitem_custom_object
940+
casted = setitem_datetimelike_compat(values, len(vi), casted)
941+
values[indexer] = casted
948942
return self
949943

950944
def putmask(self, mask, new) -> list[Block]:
@@ -1348,10 +1342,8 @@ def setitem(self, indexer, value):
13481342
`indexer` is a direct slice/positional indexer. `value` must
13491343
be a compatible shape.
13501344
"""
1351-
if not self._can_hold_element(value):
1352-
# see TestSetitemFloatIntervalWithIntIntervalValues
1353-
nb = self.coerce_to_target_dtype(value)
1354-
return nb.setitem(indexer, value)
1345+
orig_indexer = indexer
1346+
orig_value = value
13551347

13561348
indexer = self._unwrap_setitem_indexer(indexer)
13571349
value = self._maybe_squeeze_arg(value)
@@ -1362,8 +1354,26 @@ def setitem(self, indexer, value):
13621354
# unconditionally
13631355
values = values.T
13641356
check_setitem_lengths(indexer, value, values)
1365-
values[indexer] = value
1366-
return self
1357+
1358+
try:
1359+
values[indexer] = value
1360+
except (ValueError, TypeError) as err:
1361+
_catch_deprecated_value_error(err)
1362+
1363+
if is_interval_dtype(self.dtype):
1364+
# see TestSetitemFloatIntervalWithIntIntervalValues
1365+
nb = self.coerce_to_target_dtype(orig_value)
1366+
return nb.setitem(orig_indexer, orig_value)
1367+
1368+
elif isinstance(self, NDArrayBackedExtensionBlock):
1369+
nb = self.coerce_to_target_dtype(orig_value)
1370+
return nb.setitem(orig_indexer, orig_value)
1371+
1372+
else:
1373+
raise
1374+
1375+
else:
1376+
return self
13671377

13681378
def where(self, other, cond) -> list[Block]:
13691379
arr = self.values.T
@@ -1870,7 +1880,12 @@ def _catch_deprecated_value_error(err: Exception) -> None:
18701880
if isinstance(err, ValueError):
18711881
# TODO(2.0): once DTA._validate_setitem_value deprecation
18721882
# is enforced, stop catching ValueError here altogether
1873-
if "Timezones don't match" not in str(err):
1883+
if isinstance(err, IncompatibleFrequency):
1884+
pass
1885+
elif "'value.closed' is" in str(err):
1886+
# IntervalDtype mismatched 'closed'
1887+
pass
1888+
elif "Timezones don't match" not in str(err):
18741889
raise
18751890

18761891

0 commit comments

Comments
 (0)