Skip to content

Commit 8e37baa

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

File tree

5 files changed

+67
-11
lines changed

5 files changed

+67
-11
lines changed

pandas/core/base.py

+25-6
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
"""
@@ -498,8 +504,7 @@ def searchsorted(self, key, side='left'):
498504
#### needs tests/doc-string
499505
return self.values.searchsorted(key, side=side)
500506

501-
# string methods
502-
def _make_str_accessor(self):
507+
def _check_str_accessor(self):
503508
from pandas.core.series import Series
504509
from pandas.core.index import Index
505510
if isinstance(self, Series) and not com.is_object_dtype(self.dtype):
@@ -512,10 +517,24 @@ def _make_str_accessor(self):
512517
elif isinstance(self, Index) and self.inferred_type != 'string':
513518
raise AttributeError("Can only use .str accessor with string "
514519
"values (i.e. inferred_type is 'string')")
520+
521+
# string methods
522+
def _make_str_accessor(self):
523+
self._check_str_accessor()
515524
return StringMethods(self)
516525

517526
str = AccessorProperty(StringMethods, _make_str_accessor)
518527

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

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

+18
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,23 @@ def _make_cat_accessor(self):
25172518

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

2521+
def _dir_deletions(self):
2522+
return set(['str', 'cat', 'dt'])
2523+
2524+
def _dir_additions(self):
2525+
rv = set()
2526+
if is_datetimelike(self):
2527+
rv.add('dt')
2528+
elif com.is_categorical_dtype(self.dtype):
2529+
rv.add('cat')
2530+
else:
2531+
try:
2532+
self._check_str_accessor()
2533+
rv.add('str')
2534+
except AttributeError:
2535+
pass
2536+
return rv
2537+
25202538
Series._setup_axes(['index'], info_axis=0, stat_axis=0,
25212539
aliases={'rows': 0})
25222540
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)