diff --git a/pandas/core/arrays/__init__.py b/pandas/core/arrays/__init__.py index 868118bac6a7b..03d998707c26b 100644 --- a/pandas/core/arrays/__init__.py +++ b/pandas/core/arrays/__init__.py @@ -2,6 +2,7 @@ ExtensionArray, ExtensionOpsMixin, ExtensionScalarOpsMixin, + try_cast_to_ea, ) from .categorical import Categorical # noqa: F401 from .datetimes import DatetimeArray # noqa: F401 diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 08901df963f20..7333254831838 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -32,6 +32,29 @@ _extension_array_shared_docs = dict() # type: Dict[str, str] +def try_cast_to_ea(cls_or_instance, obj, dtype=None): + """ + Call to `_from_sequence` that returns the object unchanged on Exception. + + Parameters + ---------- + cls_or_instance : ExtensionArray subclass or instance + obj : arraylike + Values to pass to cls._from_sequence + dtype : ExtensionDtype, optional + + Returns + ------- + ExtensionArray or obj + """ + try: + result = cls_or_instance._from_sequence(obj, dtype=dtype) + except Exception: + # We can't predict what downstream EA constructors may raise + result = obj + return result + + class ExtensionArray: """ Abstract base class for custom 1-D array types. @@ -1156,9 +1179,9 @@ def _maybe_convert(arr): # https://github.com/pandas-dev/pandas/issues/22850 # We catch all regular exceptions here, and fall back # to an ndarray. - try: - res = self._from_sequence(arr) - except Exception: + res = try_cast_to_ea(self, arr) + if not isinstance(res, type(self)): + # exception raised in _from_sequence; ensure we have ndarray res = np.asarray(arr) else: res = np.asarray(arr) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index c50870563df28..4f37bfc52c162 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -56,7 +56,7 @@ from pandas.io.formats import console -from .base import ExtensionArray, _extension_array_shared_docs +from .base import ExtensionArray, _extension_array_shared_docs, try_cast_to_ea _take_msg = textwrap.dedent( """\ @@ -2613,10 +2613,10 @@ def _get_codes_for_values(values, categories): # Support inferring the correct extension dtype from an array of # scalar objects. e.g. # Categorical(array[Period, Period], categories=PeriodIndex(...)) - try: - values = categories.dtype.construct_array_type()._from_sequence(values) - except Exception: - # but that may fail for any reason, so fall back to object + cls = categories.dtype.construct_array_type() + values = try_cast_to_ea(cls, values) + if not isinstance(values, cls): + # exception raised in _from_sequence values = ensure_object(values) categories = ensure_object(categories) else: diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py index e53e7ffdbf72f..404da096d8535 100644 --- a/pandas/core/groupby/groupby.py +++ b/pandas/core/groupby/groupby.py @@ -43,7 +43,7 @@ class providing the base-class of operations. from pandas.core import nanops import pandas.core.algorithms as algorithms -from pandas.core.arrays import Categorical +from pandas.core.arrays import Categorical, try_cast_to_ea from pandas.core.base import DataError, PandasObject, SelectionMixin import pandas.core.common as com from pandas.core.construction import extract_array @@ -819,14 +819,8 @@ def _try_cast(self, result, obj, numeric_only=False): # if the type is compatible with the calling EA. # return the same type (Series) as our caller - try: - result = obj._values._from_sequence(result, dtype=dtype) - except Exception: - # https://github.com/pandas-dev/pandas/issues/22850 - # pandas has no control over what 3rd-party ExtensionArrays - # do in _values_from_sequence. We still want ops to work - # though, so we catch any regular Exception. - pass + cls = dtype.construct_array_type() + result = try_cast_to_ea(cls, result, dtype=dtype) elif numeric_only and is_numeric_dtype(dtype) or not numeric_only: result = maybe_downcast_to_dtype(result, dtype) diff --git a/pandas/core/series.py b/pandas/core/series.py index 3e9d3d5c04559..1a0db520f6a6a 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -55,7 +55,7 @@ import pandas as pd from pandas.core import algorithms, base, generic, nanops, ops from pandas.core.accessor import CachedAccessor -from pandas.core.arrays import ExtensionArray +from pandas.core.arrays import ExtensionArray, try_cast_to_ea from pandas.core.arrays.categorical import Categorical, CategoricalAccessor from pandas.core.arrays.sparse import SparseAccessor import pandas.core.common as com @@ -2849,14 +2849,7 @@ def combine(self, other, func, fill_value=None): elif is_extension_array_dtype(self.values): # The function can return something of any type, so check # if the type is compatible with the calling EA. - try: - new_values = self._values._from_sequence(new_values) - except Exception: - # https://github.com/pandas-dev/pandas/issues/22850 - # pandas has no control over what 3rd-party ExtensionArrays - # do in _values_from_sequence. We still want ops to work - # though, so we catch any regular Exception. - pass + new_values = try_cast_to_ea(self._values, new_values) return self._constructor(new_values, index=new_index, name=new_name) def combine_first(self, other):