Skip to content

Commit a6d0100

Browse files
jbrockmendelMateusz Górski
authored and
Mateusz Górski
committed
Implement safe_ea_cast to avoid catching Exception (pandas-dev#29293)
1 parent f447106 commit a6d0100

File tree

5 files changed

+37
-26
lines changed

5 files changed

+37
-26
lines changed

pandas/core/arrays/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
ExtensionArray,
33
ExtensionOpsMixin,
44
ExtensionScalarOpsMixin,
5+
try_cast_to_ea,
56
)
67
from .categorical import Categorical # noqa: F401
78
from .datetimes import DatetimeArray # noqa: F401

pandas/core/arrays/base.py

+26-3
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@
3232
_extension_array_shared_docs = dict() # type: Dict[str, str]
3333

3434

35+
def try_cast_to_ea(cls_or_instance, obj, dtype=None):
36+
"""
37+
Call to `_from_sequence` that returns the object unchanged on Exception.
38+
39+
Parameters
40+
----------
41+
cls_or_instance : ExtensionArray subclass or instance
42+
obj : arraylike
43+
Values to pass to cls._from_sequence
44+
dtype : ExtensionDtype, optional
45+
46+
Returns
47+
-------
48+
ExtensionArray or obj
49+
"""
50+
try:
51+
result = cls_or_instance._from_sequence(obj, dtype=dtype)
52+
except Exception:
53+
# We can't predict what downstream EA constructors may raise
54+
result = obj
55+
return result
56+
57+
3558
class ExtensionArray:
3659
"""
3760
Abstract base class for custom 1-D array types.
@@ -1156,9 +1179,9 @@ def _maybe_convert(arr):
11561179
# https://github.com/pandas-dev/pandas/issues/22850
11571180
# We catch all regular exceptions here, and fall back
11581181
# to an ndarray.
1159-
try:
1160-
res = self._from_sequence(arr)
1161-
except Exception:
1182+
res = try_cast_to_ea(self, arr)
1183+
if not isinstance(res, type(self)):
1184+
# exception raised in _from_sequence; ensure we have ndarray
11621185
res = np.asarray(arr)
11631186
else:
11641187
res = np.asarray(arr)

pandas/core/arrays/categorical.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
from pandas.io.formats import console
5858

59-
from .base import ExtensionArray, _extension_array_shared_docs
59+
from .base import ExtensionArray, _extension_array_shared_docs, try_cast_to_ea
6060

6161
_take_msg = textwrap.dedent(
6262
"""\
@@ -2613,10 +2613,10 @@ def _get_codes_for_values(values, categories):
26132613
# Support inferring the correct extension dtype from an array of
26142614
# scalar objects. e.g.
26152615
# Categorical(array[Period, Period], categories=PeriodIndex(...))
2616-
try:
2617-
values = categories.dtype.construct_array_type()._from_sequence(values)
2618-
except Exception:
2619-
# but that may fail for any reason, so fall back to object
2616+
cls = categories.dtype.construct_array_type()
2617+
values = try_cast_to_ea(cls, values)
2618+
if not isinstance(values, cls):
2619+
# exception raised in _from_sequence
26202620
values = ensure_object(values)
26212621
categories = ensure_object(categories)
26222622
else:

pandas/core/groupby/groupby.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class providing the base-class of operations.
4343

4444
from pandas.core import nanops
4545
import pandas.core.algorithms as algorithms
46-
from pandas.core.arrays import Categorical
46+
from pandas.core.arrays import Categorical, try_cast_to_ea
4747
from pandas.core.base import DataError, PandasObject, SelectionMixin
4848
import pandas.core.common as com
4949
from pandas.core.construction import extract_array
@@ -819,14 +819,8 @@ def _try_cast(self, result, obj, numeric_only=False):
819819
# if the type is compatible with the calling EA.
820820

821821
# return the same type (Series) as our caller
822-
try:
823-
result = obj._values._from_sequence(result, dtype=dtype)
824-
except Exception:
825-
# https://github.com/pandas-dev/pandas/issues/22850
826-
# pandas has no control over what 3rd-party ExtensionArrays
827-
# do in _values_from_sequence. We still want ops to work
828-
# though, so we catch any regular Exception.
829-
pass
822+
cls = dtype.construct_array_type()
823+
result = try_cast_to_ea(cls, result, dtype=dtype)
830824
elif numeric_only and is_numeric_dtype(dtype) or not numeric_only:
831825
result = maybe_downcast_to_dtype(result, dtype)
832826

pandas/core/series.py

+2-9
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
import pandas as pd
5656
from pandas.core import algorithms, base, generic, nanops, ops
5757
from pandas.core.accessor import CachedAccessor
58-
from pandas.core.arrays import ExtensionArray
58+
from pandas.core.arrays import ExtensionArray, try_cast_to_ea
5959
from pandas.core.arrays.categorical import Categorical, CategoricalAccessor
6060
from pandas.core.arrays.sparse import SparseAccessor
6161
import pandas.core.common as com
@@ -2849,14 +2849,7 @@ def combine(self, other, func, fill_value=None):
28492849
elif is_extension_array_dtype(self.values):
28502850
# The function can return something of any type, so check
28512851
# if the type is compatible with the calling EA.
2852-
try:
2853-
new_values = self._values._from_sequence(new_values)
2854-
except Exception:
2855-
# https://github.com/pandas-dev/pandas/issues/22850
2856-
# pandas has no control over what 3rd-party ExtensionArrays
2857-
# do in _values_from_sequence. We still want ops to work
2858-
# though, so we catch any regular Exception.
2859-
pass
2852+
new_values = try_cast_to_ea(self._values, new_values)
28602853
return self._constructor(new_values, index=new_index, name=new_name)
28612854

28622855
def combine_first(self, other):

0 commit comments

Comments
 (0)