Skip to content

Commit 4e7151b

Browse files
authored
BUG: inspect.getmembers(Series) (#38782)
1 parent 15974c2 commit 4e7151b

File tree

7 files changed

+28
-46
lines changed

7 files changed

+28
-46
lines changed

doc/source/development/extending.rst

+4-14
Original file line numberDiff line numberDiff line change
@@ -329,21 +329,11 @@ Each data structure has several *constructor properties* for returning a new
329329
data structure as the result of an operation. By overriding these properties,
330330
you can retain subclasses through ``pandas`` data manipulations.
331331

332-
There are 3 constructor properties to be defined:
332+
There are 3 possible constructor properties to be defined on a subclass:
333333

334-
* ``_constructor``: Used when a manipulation result has the same dimensions as the original.
335-
* ``_constructor_sliced``: Used when a manipulation result has one lower dimension(s) as the original, such as ``DataFrame`` single columns slicing.
336-
* ``_constructor_expanddim``: Used when a manipulation result has one higher dimension as the original, such as ``Series.to_frame()``.
337-
338-
Following table shows how ``pandas`` data structures define constructor properties by default.
339-
340-
=========================== ======================= =============
341-
Property Attributes ``Series`` ``DataFrame``
342-
=========================== ======================= =============
343-
``_constructor`` ``Series`` ``DataFrame``
344-
``_constructor_sliced`` ``NotImplementedError`` ``Series``
345-
``_constructor_expanddim`` ``DataFrame`` ``NotImplementedError``
346-
=========================== ======================= =============
334+
* ``DataFrame/Series._constructor``: Used when a manipulation result has the same dimension as the original.
335+
* ``DataFrame._constructor_sliced``: Used when a ``DataFrame`` (sub-)class manipulation result should be a ``Series`` (sub-)class.
336+
* ``Series._constructor_expanddim``: Used when a ``Series`` (sub-)class manipulation result should be a ``DataFrame`` (sub-)class, e.g. ``Series.to_frame()``.
347337

348338
Below example shows how to define ``SubclassedSeries`` and ``SubclassedDataFrame`` overriding constructor properties.
349339

doc/source/whatsnew/v1.3.0.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ See :ref:`install.dependencies` and :ref:`install.optional_dependencies` for mor
219219
Other API changes
220220
^^^^^^^^^^^^^^^^^
221221
- Partially initialized :class:`CategoricalDtype` (i.e. those with ``categories=None`` objects will no longer compare as equal to fully initialized dtype objects.
222-
-
222+
- 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`)
223223
-
224224

225225
.. ---------------------------------------------------------------------------
@@ -446,6 +446,7 @@ Other
446446
- Bug in :class:`Index` constructor sometimes silently ignorning a specified ``dtype`` (:issue:`38879`)
447447
- Bug in constructing a :class:`Series` from a list and a :class:`PandasDtype` (:issue:`39357`)
448448
- Bug in :class:`Styler` which caused CSS to duplicate on multiple renders. (:issue:`39395`)
449+
- ``inspect.getmembers(Series)`` no longer raises an ``AbstractMethodError`` (:issue:`38782`)
449450
- :meth:`Index.where` behavior now mirrors :meth:`Index.putmask` behavior, i.e. ``index.where(mask, other)`` matches ``index.putmask(~mask, other)`` (:issue:`39412`)
450451
- 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`)
451452
- Bug in :class:`Styler` where ``subset`` arg in methods raised an error for some valid multiindex slices (:issue:`33562`)

pandas/core/frame.py

+2-11
Original file line numberDiff line numberDiff line change
@@ -490,23 +490,14 @@ class DataFrame(NDFrame, OpsMixin):
490490
_internal_names_set = {"columns", "index"} | NDFrame._internal_names_set
491491
_typ = "dataframe"
492492
_HANDLED_TYPES = (Series, Index, ExtensionArray, np.ndarray)
493+
_accessors: Set[str] = {"sparse"}
494+
_hidden_attrs: FrozenSet[str] = NDFrame._hidden_attrs | frozenset([])
493495

494496
@property
495497
def _constructor(self) -> Type[DataFrame]:
496498
return DataFrame
497499

498500
_constructor_sliced: Type[Series] = Series
499-
_hidden_attrs: FrozenSet[str] = NDFrame._hidden_attrs | frozenset([])
500-
_accessors: Set[str] = {"sparse"}
501-
502-
@property
503-
def _constructor_expanddim(self):
504-
# GH#31549 raising NotImplementedError on a property causes trouble
505-
# for `inspect`
506-
def constructor(*args, **kwargs):
507-
raise NotImplementedError("Not supported for DataFrames!")
508-
509-
return constructor
510501

511502
# ----------------------------------------------------------------------
512503
# Constructors

pandas/core/generic.py

-16
Original file line numberDiff line numberDiff line change
@@ -375,22 +375,6 @@ def _constructor(self: FrameOrSeries) -> Type[FrameOrSeries]:
375375
"""
376376
raise AbstractMethodError(self)
377377

378-
@property
379-
def _constructor_sliced(self):
380-
"""
381-
Used when a manipulation result has one lower dimension(s) as the
382-
original, such as DataFrame single columns slicing.
383-
"""
384-
raise AbstractMethodError(self)
385-
386-
@property
387-
def _constructor_expanddim(self):
388-
"""
389-
Used when a manipulation result has one higher dimension as the
390-
original, such as Series.to_frame()
391-
"""
392-
raise NotImplementedError
393-
394378
# ----------------------------------------------------------------------
395379
# Internals
396380

pandas/core/series.py

+4
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,10 @@ def _constructor(self) -> Type[Series]:
430430

431431
@property
432432
def _constructor_expanddim(self) -> Type[DataFrame]:
433+
"""
434+
Used when a manipulation result has one higher dimension as the
435+
original, such as Series.to_frame()
436+
"""
433437
from pandas.core.frame import DataFrame
434438

435439
return DataFrame

pandas/tests/frame/test_api.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -321,12 +321,14 @@ def test_set_flags(self, allows_duplicate_labels, frame_or_series):
321321
result.iloc[key] = 10
322322
assert obj.iloc[key] == 0
323323

324-
def test_constructor_expanddim_lookup(self):
325-
# GH#33628 accessing _constructor_expanddim should not
326-
# raise NotImplementedError
324+
def test_constructor_expanddim(self):
325+
# GH#33628 accessing _constructor_expanddim should not raise NotImplementedError
326+
# GH38782 pandas has no container higher than DataFrame (two-dim), so
327+
# DataFrame._constructor_expand_dim, doesn't make sense, so is removed.
327328
df = DataFrame()
328329

329-
with pytest.raises(NotImplementedError, match="Not supported for DataFrames!"):
330+
msg = "'DataFrame' object has no attribute '_constructor_expanddim'"
331+
with pytest.raises(AttributeError, match=msg):
330332
df._constructor_expanddim(np.arange(27).reshape(3, 3, 3))
331333

332334
@skip_if_no("jinja2")

pandas/tests/series/test_api.py

+10
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import inspect
12
import pydoc
23

34
import numpy as np
45
import pytest
56

7+
from pandas.util._test_decorators import skip_if_no
8+
69
import pandas as pd
710
from pandas import DataFrame, Index, Series, date_range
811
import pandas._testing as tm
@@ -167,3 +170,10 @@ def test_attrs(self):
167170
s.attrs["version"] = 1
168171
result = s + 1
169172
assert result.attrs == {"version": 1}
173+
174+
@skip_if_no("jinja2")
175+
def test_inspect_getmembers(self):
176+
# GH38782
177+
ser = Series()
178+
with tm.assert_produces_warning(None):
179+
inspect.getmembers(ser)

0 commit comments

Comments
 (0)