Skip to content

Commit 0c7baf6

Browse files
committed
Use proper initialization of offsets after pickling
1 parent ee0405a commit 0c7baf6

File tree

3 files changed

+18
-61
lines changed

3 files changed

+18
-61
lines changed

pandas/_libs/properties.pyx

+12-12
Original file line numberDiff line numberDiff line change
@@ -23,30 +23,30 @@ cdef class CachedProperty:
2323
return self
2424

2525
# Get the cache or set a default one if needed
26-
cache = getattr(obj, '_cache', None)
26+
cache = instance_cache = getattr(obj, '_cache', None)
2727

28-
if cache is not None:
28+
if cache is None:
29+
try:
30+
cache = obj._cache = {}
31+
except (AttributeError):
32+
raise TypeError(
33+
f"Cython extension type {type(obj)} must declare attribute "
34+
"`_cache` to use @cache_readonly."
35+
)
36+
37+
if instance_cache is not None:
2938
# When accessing cython extension types, the attribute is already
3039
# registered and known to the class, unlike for python object. To
3140
# ensure we're not accidentally using a global scope / class level
3241
# cache we'll need to check whether the instance and class
3342
# attribute is identical
3443
cache_class = getattr(typ, "_cache", None)
35-
if cache_class is not None and cache_class is cache:
44+
if cache_class is not None and cache_class is instance_cache:
3645
raise TypeError(
3746
f"Class {typ} defines a `_cache` attribute on class level "
3847
"which is forbidden in combination with @cache_readonly."
3948
)
4049

41-
if cache is None:
42-
try:
43-
cache = obj._cache = {}
44-
except (AttributeError):
45-
raise TypeError(
46-
f"Cython extension type {type(obj)} must declare attribute "
47-
"`_cache` to use @cache_readonly."
48-
)
49-
5050
if PyDict_Contains(cache, self.name):
5151
# not necessary to Py_INCREF
5252
val = <object>PyDict_GetItem(cache, self.name)

pandas/_libs/tslibs/offsets.pyx

-43
Original file line numberDiff line numberDiff line change
@@ -895,13 +895,6 @@ cdef class Tick(SingleConstructorOffset):
895895

896896
raise ApplyTypeError(f"Unhandled type: {type(other).__name__}")
897897

898-
# --------------------------------------------------------------------
899-
# Pickle Methods
900-
901-
def __setstate__(self, state):
902-
self.n = state["n"]
903-
self.normalize = False
904-
905898

906899
cdef class Day(Tick):
907900
_nanos_inc = 24 * 3600 * 1_000_000_000
@@ -2005,11 +1998,6 @@ cdef class QuarterOffset(SingleConstructorOffset):
20051998
startingMonth = self._default_starting_month
20061999
self.startingMonth = startingMonth
20072000

2008-
cpdef __setstate__(self, state):
2009-
self.startingMonth = state.pop("startingMonth")
2010-
self.n = state.pop("n")
2011-
self.normalize = state.pop("normalize")
2012-
20132001
@classmethod
20142002
def _from_name(cls, suffix=None):
20152003
kwargs = {}
@@ -2265,11 +2253,6 @@ cdef class SemiMonthOffset(SingleConstructorOffset):
22652253
f"got {self.day_of_month}"
22662254
)
22672255

2268-
cpdef __setstate__(self, state):
2269-
self.n = state.pop("n")
2270-
self.normalize = state.pop("normalize")
2271-
self.day_of_month = state.pop("day_of_month")
2272-
22732256
@classmethod
22742257
def _from_name(cls, suffix=None):
22752258
return cls(day_of_month=suffix)
@@ -2587,12 +2570,6 @@ cdef class WeekOfMonth(WeekOfMonthMixin):
25872570
if self.week < 0 or self.week > 3:
25882571
raise ValueError(f"Week must be 0<=week<=3, got {self.week}")
25892572

2590-
cpdef __setstate__(self, state):
2591-
self.n = state.pop("n")
2592-
self.normalize = state.pop("normalize")
2593-
self.weekday = state.pop("weekday")
2594-
self.week = state.pop("week")
2595-
25962573
def _get_offset_day(self, other: datetime) -> int:
25972574
"""
25982575
Find the day in the same month as other that has the same
@@ -2652,12 +2629,6 @@ cdef class LastWeekOfMonth(WeekOfMonthMixin):
26522629
if self.n == 0:
26532630
raise ValueError("N cannot be 0")
26542631

2655-
cpdef __setstate__(self, state):
2656-
self.n = state.pop("n")
2657-
self.normalize = state.pop("normalize")
2658-
self.weekday = state.pop("weekday")
2659-
self.week = -1
2660-
26612632
def _get_offset_day(self, other: datetime) -> int:
26622633
"""
26632634
Find the day in the same month as other that has the same
@@ -2709,12 +2680,6 @@ cdef class FY5253Mixin(SingleConstructorOffset):
27092680
if self.variation not in ["nearest", "last"]:
27102681
raise ValueError(f"{self.variation} is not a valid variation")
27112682

2712-
cpdef __setstate__(self, state):
2713-
self.n = state.pop("n")
2714-
self.normalize = state.pop("normalize")
2715-
self.weekday = state.pop("weekday")
2716-
self.variation = state.pop("variation")
2717-
27182683
def is_anchored(self) -> bool:
27192684
return (
27202685
self.n == 1 and self.startingMonth is not None and self.weekday is not None
@@ -2995,10 +2960,6 @@ cdef class FY5253Quarter(FY5253Mixin):
29952960
)
29962961
self.qtr_with_extra_week = qtr_with_extra_week
29972962

2998-
cpdef __setstate__(self, state):
2999-
FY5253Mixin.__setstate__(self, state)
3000-
self.qtr_with_extra_week = state.pop("qtr_with_extra_week")
3001-
30022963
@cache_readonly
30032964
def _offset(self):
30042965
return FY5253(
@@ -3141,10 +3102,6 @@ cdef class Easter(SingleConstructorOffset):
31413102
Right now uses the revised method which is valid in years 1583-4099.
31423103
"""
31433104

3144-
cpdef __setstate__(self, state):
3145-
self.n = state.pop("n")
3146-
self.normalize = state.pop("normalize")
3147-
31483105
@apply_wraps
31493106
def apply(self, other: datetime) -> datetime:
31503107
current_easter = easter(other.year)

pandas/tests/libs/test_lib.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -217,25 +217,24 @@ def test_no_default_pickle():
217217

218218
def test_cache_readonly_no_global_cache():
219219
"""
220-
Ensure that the definition of a global attribute `_cache` does not interfere with
220+
Ensure that the definition of a global attribute `_cache` does
221+
not interfere with the property cache
221222
"""
222223

223224
from typing import Dict
224225

225-
# Just declaring the type is sufficient for the attribute to be considered global
226226
class MyDtypeGlobalAttr:
227-
_cache: Dict
227+
_cache: Dict = {}
228228

229229
def __init__(self, value):
230230
self._value = value
231-
self._cache = {}
232231

233232
@properties.cache_readonly
234233
def cached_attr(self):
235234
return self._value
236235

237236
a = MyDtypeGlobalAttr("A")
238-
with pytest.raises(TypeError, match=r"Class \w* defines a `_cache` " r"attribute"):
237+
with pytest.raises(TypeError, match=r"defines a `_cache` attribute"):
239238
a.cached_attr
240239

241240
class MyDtypeInstanceAttr:
@@ -251,7 +250,8 @@ def cached_attr(self):
251250
b = MyDtypeInstanceAttr("B")
252251
b._cache = {}
253252

254-
# Assert our assumption that the property cache actually uses an attribute called _cache
253+
# Assert our assumption that the property cache
254+
# actually uses an attribute called _cache
255255
assert len(a._cache) == 0
256256

257257
assert a.cached_attr == "A"

0 commit comments

Comments
 (0)