Skip to content

Commit ad6a202

Browse files
committed
PERF: Faster Series.__getattribute__
Closes pandas-dev#19764
1 parent 6cacdde commit ad6a202

File tree

12 files changed

+35
-1
lines changed

12 files changed

+35
-1
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,7 @@ Performance Improvements
958958
- Improved performance of :func:`pandas.core.groupby.GroupBy.any` and :func:`pandas.core.groupby.GroupBy.all` (:issue:`15435`)
959959
- Improved performance of :func:`pandas.core.groupby.GroupBy.pct_change` (:issue:`19165`)
960960
- Improved performance of :func:`Series.isin` in the case of categorical dtypes (:issue:`20003`)
961+
- Improved performance of ``Series.__getattribute__`` when the Series has certain index types. This manifiested in slow printing of large Series with a ``DatetimeIndex`` (:issue:`19764`)
961962
- Fixed a performance regression for :func:`GroupBy.nth` and :func:`GroupBy.last` with some object columns (:issue:`19283`)
962963

963964
.. _whatsnew_0230.docs:

pandas/core/generic.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4375,7 +4375,7 @@ def __getattr__(self, name):
43754375
name in self._accessors):
43764376
return object.__getattribute__(self, name)
43774377
else:
4378-
if name in self._info_axis:
4378+
if self._info_axis._is_dotable and name in self._info_axis:
43794379
return self[name]
43804380
return object.__getattribute__(self, name)
43814381

pandas/core/indexes/base.py

+6
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,12 @@ class Index(IndexOpsMixin, PandasObject):
244244
_engine_type = libindex.ObjectEngine
245245

246246
_accessors = set(['str'])
247+
# Whether items can be selected from NDFrame.<item>
248+
# Some indexes (DatetimeIndex, Int64Index) cannot contain
249+
# valid Python identifiers. Setting _is_dotable = False is an
250+
# optimization.
251+
# https://github.com/pandas-dev/pandas/issues/19764
252+
_is_dotable = True
247253

248254
str = CachedAccessor("str", StringMethods)
249255

pandas/core/indexes/interval.py

+1
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ class IntervalIndex(IntervalMixin, Index):
207207
_typ = 'intervalindex'
208208
_comparables = ['name']
209209
_attributes = ['name', 'closed']
210+
_is_dotable = False # can't contain Python identifiers
210211

211212
# we would like our indexing holder to defer to us
212213
_defer_to_indexing = True

pandas/core/indexes/numeric.py

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class NumericIndex(Index):
3131
3232
"""
3333
_is_numeric_dtype = True
34+
_is_dotable = False # Can't contain Python identifiers
3435

3536
def __new__(cls, data=None, dtype=None, copy=False, name=None,
3637
fastpath=False):

pandas/core/indexes/period.py

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ class PeriodIndex(DatelikeOps, DatetimeIndexOpsMixin, Int64Index):
204204
"""
205205
_typ = 'periodindex'
206206
_attributes = ['name', 'freq']
207+
_is_dotable = False # Can't contain Python identifiers
207208

208209
# define my properties & methods for delegation
209210
_other_ops = []

pandas/tests/indexes/datetimelike.py

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88

99
class DatetimeLike(Base):
1010

11+
def test_is_dotable(self):
12+
idx = self.create_index()
13+
assert idx._is_dotable is False
14+
1115
def test_shift_identity(self):
1216

1317
idx = self.create_index()

pandas/tests/indexes/test_base.py

+4
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ def generate_index_types(self, skip_index_keys=[]):
6666
if key not in skip_index_keys:
6767
yield key, idx
6868

69+
def test_is_dotable(self):
70+
idx = self.create_index()
71+
assert idx._is_dotable is True
72+
6973
def test_new_axis(self):
7074
new_index = self.dateIndex[None, :]
7175
assert new_index.ndim == 2

pandas/tests/indexes/test_category.py

+4
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def create_index(self, categories=None, ordered=False):
3333
return CategoricalIndex(
3434
list('aabbca'), categories=categories, ordered=ordered)
3535

36+
def test_is_dotable(self):
37+
ci = self.create_index(categories=list('abcd'))
38+
assert ci._is_dotable is True
39+
3640
def test_construction(self):
3741

3842
ci = self.create_index(categories=list('abcd'))

pandas/tests/indexes/test_multi.py

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ def setup_method(self, method):
4848
def create_index(self):
4949
return self.index
5050

51+
def test_is_dotable(self):
52+
idx = self.create_index()
53+
assert idx._is_dotable is True
54+
5155
def test_boolean_context_compat2(self):
5256

5357
# boolean context compat

pandas/tests/indexes/test_numeric.py

+4
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ def test_index_rdiv_timedelta(self, scalar_td, index):
6464

6565
class Numeric(Base):
6666

67+
def test_is_dotable(self):
68+
idx = self.create_index()
69+
assert idx._is_dotable is False
70+
6771
def test_numeric_compat(self):
6872
pass # override Base method
6973

pandas/tests/indexes/test_range.py

+4
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def check_binop(self, ops, scalars, idxs):
4444
expected = op(Int64Index(idx), scalar)
4545
tm.assert_index_equal(result, expected)
4646

47+
def test_is_dotable(self):
48+
idx = self.create_index()
49+
assert idx._is_dotable is False
50+
4751
def test_binops(self):
4852
ops = [operator.add, operator.sub, operator.mul, operator.floordiv,
4953
operator.truediv]

0 commit comments

Comments
 (0)