@@ -2185,80 +2185,132 @@ def can_hold_element(arr: ArrayLike, element: Any) -> bool:
2185
2185
# ExtensionBlock._can_hold_element
2186
2186
return True
2187
2187
2188
- if dtype == _dtype_obj :
2188
+ try :
2189
+ np_can_hold_element (dtype , element )
2189
2190
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
2190
2214
2191
2215
tipo = maybe_infer_dtype_type (element )
2192
2216
2193
2217
if dtype .kind in ["i" , "u" ]:
2194
2218
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
2196
2222
2197
2223
if tipo is not None :
2198
2224
if tipo .kind not in ["i" , "u" ]:
2199
2225
if is_float (element ) and element .is_integer ():
2200
- return True
2226
+ return element
2201
2227
2202
2228
if isinstance (element , np .ndarray ) and element .dtype .kind == "f" :
2203
2229
# If all can be losslessly cast to integers, then we can hold them
2204
2230
# We do something similar in putmask_smart
2205
2231
casted = element .astype (dtype )
2206
2232
comp = casted == element
2207
- return comp .all ()
2233
+ if comp .all ():
2234
+ return element
2235
+ raise ValueError
2208
2236
2209
2237
# Anything other than integer we cannot hold
2210
- return False
2238
+ raise ValueError
2211
2239
elif dtype .itemsize < tipo .itemsize :
2212
2240
if is_integer (element ):
2213
2241
# e.g. test_setitem_series_int8 if we have a python int 1
2214
2242
# tipo may be np.int32, despite the fact that it will fit
2215
2243
# in smaller int dtypes.
2216
2244
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
2219
2249
elif not isinstance (tipo , np .dtype ):
2220
2250
# i.e. nullable IntegerDtype; we can put this into an ndarray
2221
2251
# 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
2223
2257
2224
- return True
2258
+ return element
2225
2259
2226
2260
# We have not inferred an integer from the dtype
2227
2261
# 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
2229
2265
2230
2266
elif dtype .kind == "f" :
2231
2267
if tipo is not None :
2232
2268
# TODO: itemsize check?
2233
2269
if tipo .kind not in ["f" , "i" , "u" ]:
2234
2270
# Anything other than float/integer we cannot hold
2235
- return False
2271
+ raise ValueError
2236
2272
elif not isinstance (tipo , np .dtype ):
2237
2273
# i.e. nullable IntegerDtype or FloatingDtype;
2238
2274
# 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
2241
2281
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
2243
2285
2244
2286
elif dtype .kind == "c" :
2245
2287
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
2250
2294
2251
2295
elif dtype .kind == "b" :
2252
2296
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
2255
2303
2256
2304
elif dtype .kind == "S" :
2257
2305
# TODO: test tests.frame.methods.test_replace tests get here,
2258
2306
# need more targeted tests. xref phofl has a PR about this
2259
2307
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
2262
2314
2263
2315
raise NotImplementedError (dtype )
2264
2316
0 commit comments