Skip to content

REF: use try/except pattern in Block.setitem, EABackedBlock.setitem #45742

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 1 commit into from
Feb 1, 2022
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
7 changes: 6 additions & 1 deletion pandas/core/arrays/datetimelike.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,11 +389,16 @@ def __setitem__( # type: ignore[override]
# 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

no_op = check_setitem_lengths(key, value, self)

# Calling super() before the no_op short-circuit means that we raise
# on invalid 'value' even if this is a no-op, e.g. wrong-dtype empty array.
super().__setitem__(key, value)

if no_op:
return

super().__setitem__(key, value)
self._maybe_clear_freq()

def _maybe_clear_freq(self):
Expand Down
75 changes: 45 additions & 30 deletions pandas/core/internals/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
writers,
)
from pandas._libs.internals import BlockPlacement
from pandas._libs.tslibs import IncompatibleFrequency
from pandas._typing import (
ArrayLike,
DtypeObj,
Expand Down Expand Up @@ -105,11 +106,7 @@
ensure_wrapped_if_datetimelike,
extract_array,
)
from pandas.core.indexers import (
check_setitem_lengths,
is_empty_indexer,
is_scalar_indexer,
)
from pandas.core.indexers import check_setitem_lengths
import pandas.core.missing as missing

if TYPE_CHECKING:
Expand Down Expand Up @@ -913,32 +910,29 @@ def setitem(self, indexer, value):

value = self._standardize_fill_value(value)

# coerce if block dtype can store value
if not self._can_hold_element(value):
# current dtype cannot store value, coerce to common dtype
return self.coerce_to_target_dtype(value).setitem(indexer, value)

# value must be storable at this moment
values = cast(np.ndarray, self.values)
if self.ndim == 2:
values = values.T

# length checking
check_setitem_lengths(indexer, value, values)

if is_empty_indexer(indexer):
# GH#8669 empty indexers, test_loc_setitem_boolean_mask_allfalse
values[indexer] = value

elif is_scalar_indexer(indexer, self.ndim):
# setting a single element for each dim and with a rhs that could
# be e.g. a list; see GH#6043
values[indexer] = value

value = extract_array(value, extract_numpy=True)
try:
casted = np_can_hold_element(values.dtype, value)
except ValueError:
# current dtype cannot store value, coerce to common dtype
nb = self.coerce_to_target_dtype(value)
return nb.setitem(indexer, value)
else:
value = setitem_datetimelike_compat(values, len(values[indexer]), value)
values[indexer] = value

if self.dtype == _dtype_obj:
# TODO: avoid having to construct values[indexer]
vi = values[indexer]
if lib.is_list_like(vi):
# checking lib.is_scalar here fails on
# test_iloc_setitem_custom_object
casted = setitem_datetimelike_compat(values, len(vi), casted)
values[indexer] = casted
return self

def putmask(self, mask, new) -> list[Block]:
Expand Down Expand Up @@ -1342,10 +1336,8 @@ def setitem(self, indexer, value):
`indexer` is a direct slice/positional indexer. `value` must
be a compatible shape.
"""
if not self._can_hold_element(value):
# see TestSetitemFloatIntervalWithIntIntervalValues
nb = self.coerce_to_target_dtype(value)
return nb.setitem(indexer, value)
orig_indexer = indexer
orig_value = value

indexer = self._unwrap_setitem_indexer(indexer)
value = self._maybe_squeeze_arg(value)
Expand All @@ -1356,8 +1348,26 @@ def setitem(self, indexer, value):
# unconditionally
values = values.T
check_setitem_lengths(indexer, value, values)
values[indexer] = value
return self

try:
values[indexer] = value
except (ValueError, TypeError) as err:
_catch_deprecated_value_error(err)

if is_interval_dtype(self.dtype):
# see TestSetitemFloatIntervalWithIntIntervalValues
nb = self.coerce_to_target_dtype(orig_value)
return nb.setitem(orig_indexer, orig_value)

elif isinstance(self, NDArrayBackedExtensionBlock):
nb = self.coerce_to_target_dtype(orig_value)
return nb.setitem(orig_indexer, orig_value)

else:
raise

else:
return self

def where(self, other, cond) -> list[Block]:
arr = self.values.T
Expand Down Expand Up @@ -1863,7 +1873,12 @@ def _catch_deprecated_value_error(err: Exception) -> None:
if isinstance(err, ValueError):
# TODO(2.0): once DTA._validate_setitem_value deprecation
# is enforced, stop catching ValueError here altogether
if "Timezones don't match" not in str(err):
if isinstance(err, IncompatibleFrequency):
pass
elif "'value.closed' is" in str(err):
# IntervalDtype mismatched 'closed'
pass
elif "Timezones don't match" not in str(err):
raise


Expand Down