Skip to content

Commit 30297db

Browse files
authored
REF: separate numpy-specific part of can_hold_element (#45166)
1 parent ce86f55 commit 30297db

File tree

1 file changed

+74
-22
lines changed

1 file changed

+74
-22
lines changed

pandas/core/dtypes/cast.py

+74-22
Original file line numberDiff line numberDiff line change
@@ -2185,80 +2185,132 @@ def can_hold_element(arr: ArrayLike, element: Any) -> bool:
21852185
# ExtensionBlock._can_hold_element
21862186
return True
21872187

2188-
if dtype == _dtype_obj:
2188+
try:
2189+
np_can_hold_element(dtype, element)
21892190
return True
2191+
except (TypeError, ValueError):
2192+
return False
2193+
2194+
2195+
def np_can_hold_element(dtype: np.dtype, element: Any) -> Any:
2196+
"""
2197+
Raise if we cannot losslessly set this element into an ndarray with this dtype.
2198+
2199+
Specifically about places where we disagree with numpy. i.e. there are
2200+
cases where numpy will raise in doing the setitem that we do not check
2201+
for here, e.g. setting str "X" into a numeric ndarray.
2202+
2203+
Returns
2204+
-------
2205+
Any
2206+
The element, potentially cast to the dtype.
2207+
2208+
Raises
2209+
------
2210+
ValueError : If we cannot losslessly store this element with this dtype.
2211+
"""
2212+
if dtype == _dtype_obj:
2213+
return element
21902214

21912215
tipo = maybe_infer_dtype_type(element)
21922216

21932217
if dtype.kind in ["i", "u"]:
21942218
if isinstance(element, range):
2195-
return _dtype_can_hold_range(element, dtype)
2219+
if _dtype_can_hold_range(element, dtype):
2220+
return element
2221+
raise ValueError
21962222

21972223
if tipo is not None:
21982224
if tipo.kind not in ["i", "u"]:
21992225
if is_float(element) and element.is_integer():
2200-
return True
2226+
return element
22012227

22022228
if isinstance(element, np.ndarray) and element.dtype.kind == "f":
22032229
# If all can be losslessly cast to integers, then we can hold them
22042230
# We do something similar in putmask_smart
22052231
casted = element.astype(dtype)
22062232
comp = casted == element
2207-
return comp.all()
2233+
if comp.all():
2234+
return element
2235+
raise ValueError
22082236

22092237
# Anything other than integer we cannot hold
2210-
return False
2238+
raise ValueError
22112239
elif dtype.itemsize < tipo.itemsize:
22122240
if is_integer(element):
22132241
# e.g. test_setitem_series_int8 if we have a python int 1
22142242
# tipo may be np.int32, despite the fact that it will fit
22152243
# in smaller int dtypes.
22162244
info = np.iinfo(dtype)
2217-
return info.min <= element <= info.max
2218-
return False
2245+
if info.min <= element <= info.max:
2246+
return element
2247+
raise ValueError
2248+
raise ValueError
22192249
elif not isinstance(tipo, np.dtype):
22202250
# i.e. nullable IntegerDtype; we can put this into an ndarray
22212251
# losslessly iff it has no NAs
2222-
return not element._mask.any()
2252+
hasnas = element._mask.any()
2253+
# TODO: don't rely on implementation detail
2254+
if hasnas:
2255+
raise ValueError
2256+
return element
22232257

2224-
return True
2258+
return element
22252259

22262260
# We have not inferred an integer from the dtype
22272261
# check if we have a builtin int or a float equal to an int
2228-
return is_integer(element) or (is_float(element) and element.is_integer())
2262+
if is_integer(element) or (is_float(element) and element.is_integer()):
2263+
return element
2264+
raise ValueError
22292265

22302266
elif dtype.kind == "f":
22312267
if tipo is not None:
22322268
# TODO: itemsize check?
22332269
if tipo.kind not in ["f", "i", "u"]:
22342270
# Anything other than float/integer we cannot hold
2235-
return False
2271+
raise ValueError
22362272
elif not isinstance(tipo, np.dtype):
22372273
# i.e. nullable IntegerDtype or FloatingDtype;
22382274
# we can put this into an ndarray losslessly iff it has no NAs
2239-
return not element._mask.any()
2240-
return True
2275+
hasnas = element._mask.any()
2276+
# TODO: don't rely on implementation detail
2277+
if hasnas:
2278+
raise ValueError
2279+
return element
2280+
return element
22412281

2242-
return lib.is_integer(element) or lib.is_float(element)
2282+
if lib.is_integer(element) or lib.is_float(element):
2283+
return element
2284+
raise ValueError
22432285

22442286
elif dtype.kind == "c":
22452287
if tipo is not None:
2246-
return tipo.kind in ["c", "f", "i", "u"]
2247-
return (
2248-
lib.is_integer(element) or lib.is_complex(element) or lib.is_float(element)
2249-
)
2288+
if tipo.kind in ["c", "f", "i", "u"]:
2289+
return element
2290+
raise ValueError
2291+
if lib.is_integer(element) or lib.is_complex(element) or lib.is_float(element):
2292+
return element
2293+
raise ValueError
22502294

22512295
elif dtype.kind == "b":
22522296
if tipo is not None:
2253-
return tipo.kind == "b"
2254-
return lib.is_bool(element)
2297+
if tipo.kind == "b": # FIXME: wrong with BooleanArray?
2298+
return element
2299+
raise ValueError
2300+
if lib.is_bool(element):
2301+
return element
2302+
raise ValueError
22552303

22562304
elif dtype.kind == "S":
22572305
# TODO: test tests.frame.methods.test_replace tests get here,
22582306
# need more targeted tests. xref phofl has a PR about this
22592307
if tipo is not None:
2260-
return tipo.kind == "S" and tipo.itemsize <= dtype.itemsize
2261-
return isinstance(element, bytes) and len(element) <= dtype.itemsize
2308+
if tipo.kind == "S" and tipo.itemsize <= dtype.itemsize:
2309+
return element
2310+
raise ValueError
2311+
if isinstance(element, bytes) and len(element) <= dtype.itemsize:
2312+
return element
2313+
raise ValueError
22622314

22632315
raise NotImplementedError(dtype)
22642316

0 commit comments

Comments
 (0)