Skip to content

Commit 707139b

Browse files
committed
add/delete str/dt/cat dynamically from __dir__ (fix for pandas-dev#9627)
1 parent 53bc65e commit 707139b

File tree

5 files changed

+63
-11
lines changed

5 files changed

+63
-11
lines changed

pandas/core/base.py

+26-6
Original file line numberDiff line numberDiff line change
@@ -86,16 +86,23 @@ 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_additions()
104+
rv = rv - self._dir_deletions()
105+
return sorted(rv)
99106

100107
def _reset_cache(self, key=None):
101108
"""
@@ -498,8 +505,7 @@ def searchsorted(self, key, side='left'):
498505
#### needs tests/doc-string
499506
return self.values.searchsorted(key, side=side)
500507

501-
# string methods
502-
def _make_str_accessor(self):
508+
def _check_str_accessor(self):
503509
from pandas.core.series import Series
504510
from pandas.core.index import Index
505511
if isinstance(self, Series) and not com.is_object_dtype(self.dtype):
@@ -512,10 +518,24 @@ def _make_str_accessor(self):
512518
elif isinstance(self, Index) and self.inferred_type != 'string':
513519
raise AttributeError("Can only use .str accessor with string "
514520
"values (i.e. inferred_type is 'string')")
521+
522+
# string methods
523+
def _make_str_accessor(self):
524+
self._check_str_accessor()
515525
return StringMethods(self)
516526

517527
str = AccessorProperty(StringMethods, _make_str_accessor)
518528

529+
def _dir_additions(self):
530+
return set()
531+
532+
def _dir_deletions(self):
533+
try:
534+
self._check_str_accessor()
535+
except AttributeError:
536+
return set(['str'])
537+
return set()
538+
519539
_shared_docs['drop_duplicates'] = (
520540
"""Return %(klass)s with duplicate values removed
521541

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 sorted(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

+13
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
@@ -2517,6 +2518,18 @@ def _make_cat_accessor(self):
25172518

25182519
cat = base.AccessorProperty(CategoricalAccessor, _make_cat_accessor)
25192520

2521+
def _dir_deletions(self):
2522+
rv = set()
2523+
if not is_datetimelike(self):
2524+
rv.add('dt')
2525+
if not com.is_categorical_dtype(self.dtype):
2526+
rv.add('cat')
2527+
try:
2528+
self._check_str_accessor()
2529+
except AttributeError:
2530+
rv.add('str')
2531+
return rv
2532+
25202533
Series._setup_axes(['index'], info_axis=0, stat_axis=0,
25212534
aliases={'rows': 0})
25222535
Series._add_numeric_operations()

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)