Skip to content

Commit 102c332

Browse files
committed
add/delete str/dt/cat dynamically from __dir__ (fix for pandas-dev#9627)
1 parent d83bbff commit 102c332

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-9
lines changed

pandas/core/base.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,22 @@ def __unicode__(self):
8686
# Should be overwritten by base classes
8787
return object.__repr__(self)
8888

89-
def _local_dir(self):
90-
""" provide addtional __dir__ for this object """
91-
return []
89+
def _dir_additions(self):
90+
""" add addtional __dir__ for this object """
91+
return set()
92+
93+
def _dir_deletions(self):
94+
""" delete unwanted __dir__ for this object """
95+
return set()
9296

9397
def __dir__(self):
9498
"""
9599
Provide method name lookup and completion
96100
Only provide 'public' methods
97101
"""
98-
return list(sorted(list(set(dir(type(self)) + self._local_dir()))))
102+
rv = set(dir(type(self)))
103+
rv = (rv - self._dir_deletions()) | self._dir_additions()
104+
return sorted(rv)
99105

100106
def _reset_cache(self, key=None):
101107
"""
@@ -516,6 +522,16 @@ def _make_str_accessor(self):
516522

517523
str = AccessorProperty(StringMethods, _make_str_accessor)
518524

525+
def _dir_additions(self):
526+
return set()
527+
528+
def _dir_deletions(self):
529+
try:
530+
getattr(self, 'str')
531+
except AttributeError:
532+
return set(['str'])
533+
return set()
534+
519535
_shared_docs['drop_duplicates'] = (
520536
"""Return %(klass)s with duplicate values removed
521537

pandas/core/generic.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ def __unicode__(self):
146146
prepr = '[%s]' % ','.join(map(com.pprint_thing, self))
147147
return '%s(%s)' % (self.__class__.__name__, prepr)
148148

149-
def _local_dir(self):
149+
def _dir_additions(self):
150150
""" add the string-like attributes from the info_axis """
151-
return [c for c in self._info_axis
152-
if isinstance(c, string_types) and isidentifier(c)]
151+
return set([c for c in self._info_axis
152+
if isinstance(c, string_types) and isidentifier(c)])
153153

154154
@property
155155
def _constructor_sliced(self):

pandas/core/groupby.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,8 @@ def _set_result_index_ordered(self, result):
498498
result.index = self.obj.index
499499
return result
500500

501-
def _local_dir(self):
502-
return sorted(set(self.obj._local_dir() + list(self._apply_whitelist)))
501+
def _dir_additions(self):
502+
return self.obj._dir_additions() | self._apply_whitelist
503503

504504
def __getattr__(self, attr):
505505
if attr in self._internal_names_set:

pandas/core/series.py

+16
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from pandas.core.internals import SingleBlockManager
3030
from pandas.core.categorical import Categorical, CategoricalAccessor
3131
from pandas.tseries.common import (maybe_to_datetimelike,
32+
is_datetimelike,
3233
CombinedDatetimelikeProperties)
3334
from pandas.tseries.index import DatetimeIndex
3435
from pandas.tseries.tdi import TimedeltaIndex
@@ -2521,6 +2522,21 @@ def _make_cat_accessor(self):
25212522

25222523
cat = base.AccessorProperty(CategoricalAccessor, _make_cat_accessor)
25232524

2525+
def _dir_deletions(self):
2526+
return self._accessors
2527+
2528+
def _dir_additions(self):
2529+
rv = set()
2530+
# these accessors are mutually exclusive, so break loop when one exists
2531+
for accessor in self._accessors:
2532+
try:
2533+
getattr(self, accessor)
2534+
rv.add(accessor)
2535+
break
2536+
except AttributeError:
2537+
pass
2538+
return rv
2539+
25242540
Series._setup_axes(['index'], info_axis=0, stat_axis=0,
25252541
aliases={'rows': 0})
25262542
Series._add_numeric_operations()

pandas/tests/test_index.py

+7
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,13 @@ def test_str_attribute(self):
12321232
expected = Series(range(2), index=['a1', 'a2'])
12331233
tm.assert_series_equal(s[s.index.str.startswith('a')], expected)
12341234

1235+
def test_tab_completion(self):
1236+
idx = Index(list('abcd'))
1237+
self.assertTrue('str' in idx.__dir__())
1238+
1239+
idx = Index(range(4))
1240+
self.assertTrue('str' not in idx.__dir__())
1241+
12351242
def test_indexing_doesnt_change_class(self):
12361243
idx = Index([1, 2, 3, 'a', 'b', 'c'])
12371244

pandas/tests/test_series.py

+19
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,25 @@ def test_dt_accessor_api(self):
242242
s.dt
243243
self.assertFalse(hasattr(s, 'dt'))
244244

245+
def test_tab_completion(self):
246+
s = Series(list('abcd'))
247+
# Series of str values should have .str but not .dt/.cat in __dir__
248+
self.assertTrue('str' in s.__dir__())
249+
self.assertTrue('dt' not in s.__dir__())
250+
self.assertTrue('cat' not in s.__dir__())
251+
252+
# similiarly for .dt
253+
s = Series(date_range('1/1/2015', periods=5))
254+
self.assertTrue('dt' in s.__dir__())
255+
self.assertTrue('str' not in s.__dir__())
256+
self.assertTrue('cat' not in s.__dir__())
257+
258+
# similiarly for .cat
259+
s = Series(list('abbcd'), dtype="category")
260+
self.assertTrue('cat' in s.__dir__())
261+
self.assertTrue('str' not in s.__dir__())
262+
self.assertTrue('dt' not in s.__dir__())
263+
245264
def test_binop_maybe_preserve_name(self):
246265

247266
# names match, preserve

0 commit comments

Comments
 (0)