diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 7dddb9f3d6f25..4f2d80e73fedf 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -35,6 +35,7 @@ from pandas.util._decorators import ( Appender, Substitution, + cache_readonly, ) from pandas.util._validators import ( validate_bool_kwarg, @@ -1273,7 +1274,9 @@ def _concat_same_type( # such as take(), reindex(), shift(), etc. In addition, those results # will then be of the ExtensionArray subclass rather than an array # of objects - _can_hold_na = True + @cache_readonly + def _can_hold_na(self) -> bool: + return self.dtype._can_hold_na def _reduce(self, name: str, *, skipna: bool = True, **kwargs): """ diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 068f5703649fa..ae9d7dcd648e3 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -353,7 +353,6 @@ class Categorical(NDArrayBackedExtensionArray, PandasObject, ObjectStringArrayMi # tolist is not actually deprecated, just suppressed in the __dir__ _hidden_attrs = PandasObject._hidden_attrs | frozenset(["tolist"]) _typ = "categorical" - _can_hold_na = True _dtype: CategoricalDtype diff --git a/pandas/core/arrays/datetimelike.py b/pandas/core/arrays/datetimelike.py index 4b264eef4bada..17f12536b4663 100644 --- a/pandas/core/arrays/datetimelike.py +++ b/pandas/core/arrays/datetimelike.py @@ -159,6 +159,10 @@ class DatetimeLikeArrayMixin(OpsMixin, NDArrayBackedExtensionArray): _recognized_scalars: tuple[type, ...] _ndarray: np.ndarray + @cache_readonly + def _can_hold_na(self) -> bool: + return True + def __init__(self, data, dtype: Dtype | None = None, freq=None, copy=False): raise AbstractMethodError(self) diff --git a/pandas/core/dtypes/base.py b/pandas/core/dtypes/base.py index 9671c340a0a92..414c60603b9fe 100644 --- a/pandas/core/dtypes/base.py +++ b/pandas/core/dtypes/base.py @@ -367,6 +367,13 @@ def _get_common_dtype(self, dtypes: list[DtypeObj]) -> DtypeObj | None: else: return None + @property + def _can_hold_na(self) -> bool: + """ + Can arrays of this dtype hold NA values? + """ + return True + def register_extension_dtype(cls: type[E]) -> type[E]: """ diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index c7769046c70b2..9a1be4d010196 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -175,15 +175,15 @@ def is_view(self) -> bool: return values.base is not None @final - @property + @cache_readonly def _can_hold_na(self) -> bool: """ Can we store NA values in this Block? """ - values = self.values - if isinstance(values, np.ndarray): - return values.dtype.kind not in ["b", "i", "u"] - return values._can_hold_na + dtype = self.dtype + if isinstance(dtype, np.dtype): + return dtype.kind not in ["b", "i", "u"] + return dtype._can_hold_na @final @cache_readonly diff --git a/pandas/tests/extension/test_external_block.py b/pandas/tests/extension/test_external_block.py index 2402c70a166b7..13dec96b144ff 100644 --- a/pandas/tests/extension/test_external_block.py +++ b/pandas/tests/extension/test_external_block.py @@ -14,9 +14,11 @@ class CustomBlock(ExtensionBlock): _holder = np.ndarray - # error: Cannot override final attribute "_can_hold_na" - # (previously declared in base class "Block") - _can_hold_na = False # type: ignore[misc] + + # Cannot override final attribute "_can_hold_na" + @property # type: ignore[misc] + def _can_hold_na(self) -> bool: + return False @pytest.fixture