diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index a963442ecda1c..66ab954bae288 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -492,6 +492,7 @@ Other - Fixed metadata propagation in the :class:`Series.dt` and :class:`Series.str` accessors (:issue:`28283`) - Bug in :meth:`Index.union` behaving differently depending on whether operand is a :class:`Index` or other list-like (:issue:`36384`) - Passing an array with 2 or more dimensions to the :class:`Series` constructor now raises the more specific ``ValueError``, from a bare ``Exception`` previously (:issue:`35744`) +- Bug in ``accessor.DirNamesMixin``, where ``dir(obj)`` wouldn't show attributes defined on the instance (:issue:`37173`). .. --------------------------------------------------------------------------- diff --git a/pandas/core/accessor.py b/pandas/core/accessor.py index 2caf1f75f3da1..41212fd49113d 100644 --- a/pandas/core/accessor.py +++ b/pandas/core/accessor.py @@ -4,7 +4,7 @@ that can be mixed into or pinned onto other pandas classes. """ -from typing import FrozenSet, Set +from typing import FrozenSet, List, Set import warnings from pandas.util._decorators import doc @@ -12,15 +12,15 @@ class DirNamesMixin: _accessors: Set[str] = set() - _deprecations: FrozenSet[str] = frozenset() + _hidden_attrs: FrozenSet[str] = frozenset() - def _dir_deletions(self): + def _dir_deletions(self) -> Set[str]: """ Delete unwanted __dir__ for this object. """ - return self._accessors | self._deprecations + return self._accessors | self._hidden_attrs - def _dir_additions(self): + def _dir_additions(self) -> Set[str]: """ Add additional __dir__ for this object. """ @@ -33,7 +33,7 @@ def _dir_additions(self): pass return rv - def __dir__(self): + def __dir__(self) -> List[str]: """ Provide method name lookup and completion. @@ -41,7 +41,7 @@ def __dir__(self): ----- Only provide 'public' methods. """ - rv = set(dir(type(self))) + rv = set(super().__dir__()) rv = (rv - self._dir_deletions()) | self._dir_additions() return sorted(rv) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 081a363ce03c6..62dd3f89770cd 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -288,7 +288,7 @@ class Categorical(NDArrayBackedExtensionArray, PandasObject, ObjectStringArrayMi __array_priority__ = 1000 _dtype = CategoricalDtype(ordered=False) # tolist is not actually deprecated, just suppressed in the __dir__ - _deprecations = PandasObject._deprecations | frozenset(["tolist"]) + _hidden_attrs = PandasObject._hidden_attrs | frozenset(["tolist"]) _typ = "categorical" _can_hold_na = True diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index 2e62fade93dcb..4346e02069667 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -270,7 +270,7 @@ class SparseArray(OpsMixin, PandasObject, ExtensionArray): """ _subtyp = "sparse_array" # register ABCSparseArray - _deprecations = PandasObject._deprecations | frozenset(["get_values"]) + _hidden_attrs = PandasObject._hidden_attrs | frozenset(["get_values"]) _sparse_index: SparseIndex def __init__( diff --git a/pandas/core/base.py b/pandas/core/base.py index 6af537dcd149a..55cb671278050 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -314,7 +314,7 @@ class IndexOpsMixin(OpsMixin): # ndarray compatibility __array_priority__ = 1000 - _deprecations: FrozenSet[str] = frozenset( + _hidden_attrs: FrozenSet[str] = frozenset( ["tolist"] # tolist is not deprecated, just suppressed in the __dir__ ) diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 539275c7ff617..2efad0210778c 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -420,7 +420,7 @@ def _constructor(self) -> Type[DataFrame]: return DataFrame _constructor_sliced: Type[Series] = Series - _deprecations: FrozenSet[str] = NDFrame._deprecations | frozenset([]) + _hidden_attrs: FrozenSet[str] = NDFrame._hidden_attrs | frozenset([]) _accessors: Set[str] = {"sparse"} @property diff --git a/pandas/core/generic.py b/pandas/core/generic.py index a8586e3b90083..8b2d4ee485202 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -197,7 +197,7 @@ class NDFrame(PandasObject, SelectionMixin, indexing.IndexingMixin): ] _internal_names_set: Set[str] = set(_internal_names) _accessors: Set[str] = set() - _deprecations: FrozenSet[str] = frozenset(["get_values", "tshift"]) + _hidden_attrs: FrozenSet[str] = frozenset(["get_values", "tshift"]) _metadata: List[str] = [] _is_copy = None _mgr: BlockManager diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py index 4ab40e25db84e..b26b48910f59b 100644 --- a/pandas/core/groupby/groupby.py +++ b/pandas/core/groupby/groupby.py @@ -489,6 +489,21 @@ def group_selection_context(groupby: "BaseGroupBy") -> Iterator["BaseGroupBy"]: class BaseGroupBy(PandasObject, SelectionMixin, Generic[FrameOrSeries]): _group_selection: Optional[IndexLabel] = None _apply_allowlist: FrozenSet[str] = frozenset() + _hidden_attrs = PandasObject._hidden_attrs | { + "as_index", + "axis", + "dropna", + "exclusions", + "grouper", + "group_keys", + "keys", + "level", + "mutated", + "obj", + "observed", + "sort", + "squeeze", + } def __init__( self, diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py index b9b2c4b07d37a..1dd85d072f253 100644 --- a/pandas/core/indexes/accessors.py +++ b/pandas/core/indexes/accessors.py @@ -28,6 +28,11 @@ class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin): + _hidden_attrs = PandasObject._hidden_attrs | { + "orig", + "name", + } + def __init__(self, data: "Series", orig): if not isinstance(data, ABCSeries): raise TypeError( diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 6b71e455782e3..4bcc237c996ad 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -193,9 +193,9 @@ class Index(IndexOpsMixin, PandasObject): """ # tolist is not actually deprecated, just suppressed in the __dir__ - _deprecations: FrozenSet[str] = ( - PandasObject._deprecations - | IndexOpsMixin._deprecations + _hidden_attrs: FrozenSet[str] = ( + PandasObject._hidden_attrs + | IndexOpsMixin._hidden_attrs | frozenset(["contains", "set_value"]) ) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index d012d5704f716..f7d8bb3601ec5 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -258,7 +258,7 @@ class MultiIndex(Index): of the mentioned helper methods. """ - _deprecations = Index._deprecations | frozenset() + _hidden_attrs = Index._hidden_attrs | frozenset() # initialize to zero-length tuples to make everything work _typ = "multiindex" diff --git a/pandas/core/series.py b/pandas/core/series.py index 55aa32dd028ef..c9b307d6c4380 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -181,9 +181,9 @@ class Series(base.IndexOpsMixin, generic.NDFrame): _metadata: List[str] = ["name"] _internal_names_set = {"index"} | generic.NDFrame._internal_names_set _accessors = {"dt", "cat", "str", "sparse"} - _deprecations = ( - base.IndexOpsMixin._deprecations - | generic.NDFrame._deprecations + _hidden_attrs = ( + base.IndexOpsMixin._hidden_attrs + | generic.NDFrame._hidden_attrs | frozenset(["compress", "ptp"]) ) diff --git a/pandas/tests/test_register_accessor.py b/pandas/tests/test_register_accessor.py index d839936f731a3..6e224245076ee 100644 --- a/pandas/tests/test_register_accessor.py +++ b/pandas/tests/test_register_accessor.py @@ -4,6 +4,22 @@ import pandas as pd import pandas._testing as tm +from pandas.core import accessor + + +def test_dirname_mixin(): + # GH37173 + + class X(accessor.DirNamesMixin): + x = 1 + y: int + + def __init__(self): + self.z = 3 + + result = [attr_name for attr_name in dir(X()) if not attr_name.startswith("_")] + + assert result == ["x", "z"] @contextlib.contextmanager