diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 97a764fa7dbe8..0e1c643d58596 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -5,6 +5,19 @@ This is an experimental API and subject to breaking changes without warning. """ +# Some notes, just for implementors. +# +# * Avoid a `.values` attribute +# There are corners in pandas that assume assume array.values is an ndarray, +# and does ndarray things like ge the dtype. If you have a non-ndarray +# `.values` attribute, things may break. We're working to fix these. +# * Reference the default implementations, but avoid using private methods. +# Some default implementations (e.g. `factorize`) use pandas' internal +# methods. If possible, avoid using those methods when overriding the default +# implementation. If that isn't possible, raise an issue to discuss adding +# those to the public API. + + import numpy as np from pandas.errors import AbstractMethodError diff --git a/pandas/tests/extension/base/interface.py b/pandas/tests/extension/base/interface.py index 2162552e9650d..8664cd311a859 100644 --- a/pandas/tests/extension/base/interface.py +++ b/pandas/tests/extension/base/interface.py @@ -50,3 +50,15 @@ def test_is_extension_array_dtype(self, data): assert is_extension_array_dtype(data.dtype) assert is_extension_array_dtype(pd.Series(data)) assert isinstance(data.dtype, ExtensionDtype) + + def test_no_values_attribute(self, data): + # GH-20735 + # Currently, pandas has places where we accepts Union[ndarray, EA] + # but in practice expect an ndarray, since we get `data.values.dtype`. + # This warns users to avoid using a `.values` attribute that is not + # an ndarray-like + if hasattr(data, 'values') and not hasattr(data.values, 'dtype'): + msg = ("ExtensionArray contains a 'values' attribute that does " + "not have a dtype attribute. This may cause issues in " + "pandas' internals.") + raise ValueError(msg) diff --git a/pandas/tests/extension/test_values.py b/pandas/tests/extension/test_values.py new file mode 100644 index 0000000000000..754be455a6ed9 --- /dev/null +++ b/pandas/tests/extension/test_values.py @@ -0,0 +1,23 @@ +import pytest + +from pandas.tests.extension.base import BaseInterfaceTests +from pandas.tests.extension.json.array import JSONArray + + +class JSONArray2(JSONArray): + @property + def values(self): + return self.data + + +@pytest.fixture +def data(): + return JSONArray2([{"A": [1, 2], "B": [3, 4]}]) + + +class TestBaseInterfaceTests(BaseInterfaceTests): + def test_no_values_attribute(self, data): + # GH-20735 + with pytest.raises(ValueError) as m: + super(TestBaseInterfaceTests, self).test_no_values_attribute(data) + assert m.match("ExtensionArray contains")