@@ -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