diff --git a/doc/source/development/extending.rst b/doc/source/development/extending.rst index d4219296f5795..9a8a95bec66ad 100644 --- a/doc/source/development/extending.rst +++ b/doc/source/development/extending.rst @@ -329,21 +329,11 @@ Each data structure has several *constructor properties* for returning a new data structure as the result of an operation. By overriding these properties, you can retain subclasses through ``pandas`` data manipulations. -There are 3 constructor properties to be defined: +There are 3 possible constructor properties to be defined on a subclass: -* ``_constructor``: Used when a manipulation result has the same dimensions as the original. -* ``_constructor_sliced``: Used when a manipulation result has one lower dimension(s) as the original, such as ``DataFrame`` single columns slicing. -* ``_constructor_expanddim``: Used when a manipulation result has one higher dimension as the original, such as ``Series.to_frame()``. - -Following table shows how ``pandas`` data structures define constructor properties by default. - -=========================== ======================= ============= -Property Attributes ``Series`` ``DataFrame`` -=========================== ======================= ============= -``_constructor`` ``Series`` ``DataFrame`` -``_constructor_sliced`` ``NotImplementedError`` ``Series`` -``_constructor_expanddim`` ``DataFrame`` ``NotImplementedError`` -=========================== ======================= ============= +* ``DataFrame/Series._constructor``: Used when a manipulation result has the same dimension as the original. +* ``DataFrame._constructor_sliced``: Used when a ``DataFrame`` (sub-)class manipulation result should be a ``Series`` (sub-)class. +* ``Series._constructor_expanddim``: Used when a ``Series`` (sub-)class manipulation result should be a ``DataFrame`` (sub-)class, e.g. ``Series.to_frame()``. Below example shows how to define ``SubclassedSeries`` and ``SubclassedDataFrame`` overriding constructor properties. diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 17d8c79994dbe..a497f2167ca5d 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -219,7 +219,7 @@ See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for mor Other API changes ^^^^^^^^^^^^^^^^^ - Partially initialized :class:`CategoricalDtype` (i.e. those with ``categories=None`` objects will no longer compare as equal to fully initialized dtype objects. -- +- Accessing ``_constructor_expanddim`` on a :class:`DataFrame` and ``_constructor_sliced`` on a :class:`Series` now raise an ``AttributeError``. Previously a ``NotImplementedError`` was raised (:issue:`38782`) - .. --------------------------------------------------------------------------- @@ -445,6 +445,7 @@ Other - Bug in :class:`Index` constructor sometimes silently ignorning a specified ``dtype`` (:issue:`38879`) - Bug in constructing a :class:`Series` from a list and a :class:`PandasDtype` (:issue:`39357`) - Bug in :class:`Styler` which caused CSS to duplicate on multiple renders. (:issue:`39395`) +- ``inspect.getmembers(Series)`` no longer raises an ``AbstractMethodError`` (:issue:`38782`) - :meth:`Index.where` behavior now mirrors :meth:`Index.putmask` behavior, i.e. ``index.where(mask, other)`` matches ``index.putmask(~mask, other)`` (:issue:`39412`) - Bug in :func:`pandas.testing.assert_series_equal`, :func:`pandas.testing.assert_frame_equal`, :func:`pandas.testing.assert_index_equal` and :func:`pandas.testing.assert_extension_array_equal` incorrectly raising when an attribute has an unrecognized NA type (:issue:`39461`) - Bug in :class:`Styler` where ``subset`` arg in methods raised an error for some valid multiindex slices (:issue:`33562`) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 6357b8feb348b..7136e42c4f638 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -490,23 +490,14 @@ class DataFrame(NDFrame, OpsMixin): _internal_names_set = {"columns", "index"} | NDFrame._internal_names_set _typ = "dataframe" _HANDLED_TYPES = (Series, Index, ExtensionArray, np.ndarray) + _accessors: Set[str] = {"sparse"} + _hidden_attrs: FrozenSet[str] = NDFrame._hidden_attrs | frozenset([]) @property def _constructor(self) -> Type[DataFrame]: return DataFrame _constructor_sliced: Type[Series] = Series - _hidden_attrs: FrozenSet[str] = NDFrame._hidden_attrs | frozenset([]) - _accessors: Set[str] = {"sparse"} - - @property - def _constructor_expanddim(self): - # GH#31549 raising NotImplementedError on a property causes trouble - # for `inspect` - def constructor(*args, **kwargs): - raise NotImplementedError("Not supported for DataFrames!") - - return constructor # ---------------------------------------------------------------------- # Constructors diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 96b35f1aaab9c..330c945ef19cb 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -375,22 +375,6 @@ def _constructor(self: FrameOrSeries) -> Type[FrameOrSeries]: """ raise AbstractMethodError(self) - @property - def _constructor_sliced(self): - """ - Used when a manipulation result has one lower dimension(s) as the - original, such as DataFrame single columns slicing. - """ - raise AbstractMethodError(self) - - @property - def _constructor_expanddim(self): - """ - Used when a manipulation result has one higher dimension as the - original, such as Series.to_frame() - """ - raise NotImplementedError - # ---------------------------------------------------------------------- # Internals diff --git a/pandas/core/series.py b/pandas/core/series.py index 8bd325beede65..cb161a1a717cc 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -403,6 +403,10 @@ def _constructor(self) -> Type[Series]: @property def _constructor_expanddim(self) -> Type[DataFrame]: + """ + Used when a manipulation result has one higher dimension as the + original, such as Series.to_frame() + """ from pandas.core.frame import DataFrame return DataFrame diff --git a/pandas/tests/frame/test_api.py b/pandas/tests/frame/test_api.py index 29a2d9c17202e..6b8284908213a 100644 --- a/pandas/tests/frame/test_api.py +++ b/pandas/tests/frame/test_api.py @@ -321,12 +321,14 @@ def test_set_flags(self, allows_duplicate_labels, frame_or_series): result.iloc[key] = 10 assert obj.iloc[key] == 0 - def test_constructor_expanddim_lookup(self): - # GH#33628 accessing _constructor_expanddim should not - # raise NotImplementedError + def test_constructor_expanddim(self): + # GH#33628 accessing _constructor_expanddim should not raise NotImplementedError + # GH38782 pandas has no container higher than DataFrame (two-dim), so + # DataFrame._constructor_expand_dim, doesn't make sense, so is removed. df = DataFrame() - with pytest.raises(NotImplementedError, match="Not supported for DataFrames!"): + msg = "'DataFrame' object has no attribute '_constructor_expanddim'" + with pytest.raises(AttributeError, match=msg): df._constructor_expanddim(np.arange(27).reshape(3, 3, 3)) @skip_if_no("jinja2") diff --git a/pandas/tests/series/test_api.py b/pandas/tests/series/test_api.py index 2f255d92d86e3..4dd91b942474a 100644 --- a/pandas/tests/series/test_api.py +++ b/pandas/tests/series/test_api.py @@ -1,8 +1,11 @@ +import inspect import pydoc import numpy as np import pytest +from pandas.util._test_decorators import skip_if_no + import pandas as pd from pandas import DataFrame, Index, Series, date_range import pandas._testing as tm @@ -167,3 +170,10 @@ def test_attrs(self): s.attrs["version"] = 1 result = s + 1 assert result.attrs == {"version": 1} + + @skip_if_no("jinja2") + def test_inspect_getmembers(self): + # GH38782 + ser = Series() + with tm.assert_produces_warning(None): + inspect.getmembers(ser)