Skip to content

Commit a888af5

Browse files
committed
Invalidate cache whenever an attribute is re-set
1 parent 83dc7de commit a888af5

File tree

2 files changed

+28
-46
lines changed

2 files changed

+28
-46
lines changed

pandas/tests/tseries/test_offsets.py

+8-14
Original file line numberDiff line numberDiff line change
@@ -162,26 +162,20 @@ def test_apply_out_of_range(self):
162162
"cannot create out_of_range offset: {0} {1}".format(
163163
str(self).split('.')[-1], e))
164164

165-
def test_immutable(self):
165+
def test_cache_invalidation(self):
166166
if self._offset is None:
167167
return
168168
elif not issubclass(self._offset, DateOffset):
169169
raise TypeError(self._offset)
170170

171171
offset = self._get_offset(self._offset, value=14)
172-
with pytest.raises(TypeError):
173-
offset.n = 3
174-
with pytest.raises(TypeError):
175-
offset._offset = timedelta(4)
176-
with pytest.raises(TypeError):
177-
offset.normalize = True
178-
179-
if hasattr(offset, '_inc'):
180-
with pytest.raises(TypeError):
181-
offset._inc = 'foo'
182-
if hasattr(offset, 'delta'):
183-
with pytest.raises(TypeError):
184-
offset.delta = 6 * offset._inc
172+
if len(offset.kwds) == 0:
173+
cached_params = offset._params()
174+
assert '_cached_params' in offset._cache
175+
176+
offset.n = offset.n + 3
177+
# Setting offset.n should clear the cache
178+
assert len(offset._cache) == 0
185179

186180

187181
class TestCommon(Base):

pandas/tseries/offsets.py

+20-32
Original file line numberDiff line numberDiff line change
@@ -134,13 +134,6 @@ class CacheableOffset(object):
134134
_cacheable = True
135135

136136

137-
_kwds_use_relativedelta = (
138-
'years', 'months', 'weeks', 'days',
139-
'year', 'month', 'week', 'day', 'weekday',
140-
'hour', 'minute', 'second', 'microsecond'
141-
)
142-
143-
144137
def _determine_offset(kwds):
145138
# timedelta is used for sub-daily plural offsets and all singular
146139
# offsets relativedelta is used for plural offsets of daily length or
@@ -150,6 +143,11 @@ def _determine_offset(kwds):
150143
if k not in ('nanosecond', 'nanoseconds')
151144
)
152145

146+
_kwds_use_relativedelta = (
147+
'years', 'months', 'weeks', 'days',
148+
'year', 'month', 'week', 'day', 'weekday',
149+
'hour', 'minute', 'second', 'microsecond'
150+
)
153151
if len(kwds_no_nanos) > 0:
154152
if any(k in _kwds_use_relativedelta for k in kwds_no_nanos):
155153
offset = relativedelta(**kwds_no_nanos)
@@ -209,22 +207,25 @@ def __add__(date):
209207
_adjust_dst = False
210208
_typ = "dateoffset"
211209

210+
# default for prior pickles
211+
normalize = False
212+
212213
def __setattr__(self, name, value):
213214
# DateOffset needs to be effectively immutable in order for the
214215
# caching in _cached_params to be correct.
215-
frozen = ['n', '_offset', 'normalize', '_inc']
216-
if name in frozen and hasattr(self, name):
217-
msg = '%s cannot change attribute %s' % (self.__class__, name)
218-
raise TypeError(msg)
216+
if hasattr(self, name):
217+
# Resetting any existing attribute clears the cache_readonly
218+
# cache.
219+
try:
220+
cache = self._cache
221+
except AttributeError:
222+
# if the cache_readonly cache has not been accessed yet,
223+
# this attribute may not exist
224+
pass
225+
else:
226+
cache.clear()
219227
object.__setattr__(self, name, value)
220228

221-
def __setstate__(self, state):
222-
"""Reconstruct an instance from a pickled state"""
223-
self.__dict__ = state
224-
if 'normalize' not in state and not hasattr(self, 'normalize'):
225-
# default for prior pickles
226-
self.normalize = False
227-
228229
def __init__(self, n=1, normalize=False, **kwds):
229230
self.n = int(n)
230231
self.normalize = normalize
@@ -618,10 +619,6 @@ def __setstate__(self, state):
618619
self.kwds['holidays'] = self.holidays = holidays
619620
self.kwds['weekmask'] = state['weekmask']
620621

621-
if 'normalize' not in state:
622-
# default for prior pickles
623-
self.normalize = False
624-
625622

626623
class BusinessDay(BusinessMixin, SingleConstructorOffset):
627624
"""
@@ -635,7 +632,6 @@ def __init__(self, n=1, normalize=False, **kwds):
635632
self.normalize = normalize
636633
self.kwds = kwds
637634
self.offset = kwds.get('offset', timedelta(0))
638-
self._offset = None
639635

640636
@property
641637
def freqstr(self):
@@ -754,7 +750,6 @@ def __init__(self, **kwds):
754750
self.offset = kwds.get('offset', timedelta(0))
755751
self.start = kwds.get('start', '09:00')
756752
self.end = kwds.get('end', '17:00')
757-
self._offset = None
758753

759754
def _validate_time(self, t_input):
760755
from datetime import time as dt_time
@@ -1032,7 +1027,6 @@ def __init__(self, n=1, normalize=False, weekmask='Mon Tue Wed Thu Fri',
10321027
self.kwds['weekmask'] = self.weekmask = weekmask
10331028
self.kwds['holidays'] = self.holidays = holidays
10341029
self.kwds['calendar'] = self.calendar = calendar
1035-
self._offset = None
10361030

10371031
def get_calendar(self, weekmask, holidays, calendar):
10381032
"""Generate busdaycalendar"""
@@ -1229,7 +1223,6 @@ def __init__(self, n=1, day_of_month=None, normalize=False, **kwds):
12291223
self.normalize = normalize
12301224
self.kwds = kwds
12311225
self.kwds['day_of_month'] = self.day_of_month
1232-
self._offset = None
12331226

12341227
@classmethod
12351228
def _from_name(cls, suffix=None):
@@ -1628,7 +1621,6 @@ def __init__(self, n=1, normalize=False, **kwds):
16281621

16291622
self._inc = timedelta(weeks=1)
16301623
self.kwds = kwds
1631-
self._offset = None
16321624

16331625
def isAnchored(self):
16341626
return (self.n == 1 and self.weekday is not None)
@@ -1751,7 +1743,6 @@ def __init__(self, n=1, normalize=False, **kwds):
17511743
self.week)
17521744

17531745
self.kwds = kwds
1754-
self._offset = None
17551746

17561747
@apply_wraps
17571748
def apply(self, other):
@@ -1842,7 +1833,6 @@ def __init__(self, n=1, normalize=False, **kwds):
18421833
self.weekday)
18431834

18441835
self.kwds = kwds
1845-
self._offset = None
18461836

18471837
@apply_wraps
18481838
def apply(self, other):
@@ -1908,8 +1898,8 @@ def __init__(self, n=1, normalize=False, **kwds):
19081898
self.normalize = normalize
19091899
self.startingMonth = kwds.get('startingMonth',
19101900
self._default_startingMonth)
1901+
19111902
self.kwds = kwds
1912-
self._offset = None
19131903

19141904
def isAnchored(self):
19151905
return (self.n == 1 and self.startingMonth is not None)
@@ -2032,7 +2022,6 @@ def __init__(self, n=1, normalize=False, **kwds):
20322022
self.startingMonth = kwds.get('startingMonth', 3)
20332023

20342024
self.kwds = kwds
2035-
self._offset = None
20362025

20372026
def isAnchored(self):
20382027
return (self.n == 1 and self.startingMonth is not None)
@@ -2358,7 +2347,6 @@ def __init__(self, n=1, normalize=False, **kwds):
23582347
self.variation = kwds["variation"]
23592348

23602349
self.kwds = kwds
2361-
self._offset = None
23622350

23632351
if self.n == 0:
23642352
raise ValueError('N cannot be 0')

0 commit comments

Comments
 (0)