Skip to content

Commit 1f71d30

Browse files
authored
REF: make SingleConstructorOffset a cdef class (#34323)
1 parent 6551baa commit 1f71d30

File tree

2 files changed

+29
-59
lines changed

2 files changed

+29
-59
lines changed

pandas/_libs/tslibs/offsets.pyx

+21-21
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ cimport numpy as cnp
1818
from numpy cimport int64_t
1919
cnp.import_array()
2020

21+
# TODO: formalize having _libs.properties "above" tslibs in the dependency structure
22+
from pandas._libs.properties import cache_readonly
2123

2224
from pandas._libs.tslibs cimport util
2325
from pandas._libs.tslibs.util cimport is_integer_object, is_datetime64_object
@@ -447,7 +449,7 @@ cdef class BaseOffset:
447449
def __hash__(self):
448450
return hash(self._params)
449451

450-
@property
452+
@cache_readonly
451453
def _params(self):
452454
"""
453455
Returns a tuple containing all of the attributes needed to evaluate
@@ -583,7 +585,7 @@ cdef class BaseOffset:
583585
def rule_code(self) -> str:
584586
return self._prefix
585587

586-
@property
588+
@cache_readonly
587589
def freqstr(self) -> str:
588590
try:
589591
code = self.rule_code
@@ -778,7 +780,19 @@ cdef class BaseOffset:
778780
return self.n == 1
779781

780782

781-
cdef class Tick(BaseOffset):
783+
cdef class SingleConstructorOffset(BaseOffset):
784+
@classmethod
785+
def _from_name(cls, suffix=None):
786+
# default _from_name calls cls with no args
787+
if suffix:
788+
raise ValueError(f"Bad freq suffix {suffix}")
789+
return cls()
790+
791+
792+
# ---------------------------------------------------------------------
793+
# Tick Offsets
794+
795+
cdef class Tick(SingleConstructorOffset):
782796
# ensure that reversed-ops with numpy scalars return NotImplemented
783797
__array_priority__ = 1000
784798
_adjust_dst = False
@@ -796,13 +810,6 @@ cdef class Tick(BaseOffset):
796810
"Tick offset with `normalize=True` are not allowed."
797811
)
798812

799-
@classmethod
800-
def _from_name(cls, suffix=None):
801-
# default _from_name calls cls with no args
802-
if suffix:
803-
raise ValueError(f"Bad freq suffix {suffix}")
804-
return cls()
805-
806813
def _repr_attrs(self) -> str:
807814
# Since cdef classes have no __dict__, we need to override
808815
return ""
@@ -981,7 +988,7 @@ def delta_to_tick(delta: timedelta) -> Tick:
981988
# --------------------------------------------------------------------
982989

983990

984-
class BusinessMixin(BaseOffset):
991+
class BusinessMixin(SingleConstructorOffset):
985992
"""
986993
Mixin to business types to provide related functions.
987994
"""
@@ -1060,13 +1067,6 @@ class BusinessHourMixin(BusinessMixin):
10601067
object.__setattr__(self, "start", start)
10611068
object.__setattr__(self, "end", end)
10621069

1063-
@classmethod
1064-
def _from_name(cls, suffix=None):
1065-
# default _from_name calls cls with no args
1066-
if suffix:
1067-
raise ValueError(f"Bad freq suffix {suffix}")
1068-
return cls()
1069-
10701070
def _repr_attrs(self) -> str:
10711071
out = super()._repr_attrs()
10721072
hours = ",".join(
@@ -1128,7 +1128,7 @@ class CustomMixin:
11281128
object.__setattr__(self, "calendar", calendar)
11291129

11301130

1131-
class WeekOfMonthMixin(BaseOffset):
1131+
class WeekOfMonthMixin(SingleConstructorOffset):
11321132
"""
11331133
Mixin for methods common to WeekOfMonth and LastWeekOfMonth.
11341134
"""
@@ -1169,7 +1169,7 @@ class WeekOfMonthMixin(BaseOffset):
11691169

11701170
# ----------------------------------------------------------------------
11711171

1172-
cdef class YearOffset(BaseOffset):
1172+
cdef class YearOffset(SingleConstructorOffset):
11731173
"""
11741174
DateOffset that just needs a month.
11751175
"""
@@ -1230,7 +1230,7 @@ cdef class YearOffset(BaseOffset):
12301230
return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype)
12311231

12321232

1233-
cdef class QuarterOffset(BaseOffset):
1233+
cdef class QuarterOffset(SingleConstructorOffset):
12341234
_attributes = frozenset(["n", "normalize", "startingMonth"])
12351235
# TODO: Consider combining QuarterOffset and YearOffset __init__ at some
12361236
# point. Also apply_index, is_on_offset, rule_code if

pandas/tseries/offsets.py

+8-38
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
Minute,
2626
Nano,
2727
Second,
28+
SingleConstructorOffset,
2829
Tick,
2930
apply_index_wraps,
3031
apply_wraps,
@@ -298,37 +299,6 @@ def is_on_offset(self, dt):
298299
# TODO, see #1395
299300
return True
300301

301-
@cache_readonly
302-
def _params(self):
303-
# TODO: see if we can just write cache_readonly(BaseOffset._params.__get__)
304-
return BaseOffset._params.__get__(self)
305-
306-
@cache_readonly
307-
def freqstr(self):
308-
# TODO: see if we can just write cache_readonly(BaseOffset.freqstr.__get__)
309-
return BaseOffset.freqstr.__get__(self)
310-
311-
312-
class SingleConstructorMixin:
313-
@cache_readonly
314-
def _params(self):
315-
# TODO: see if we can just write cache_readonly(BaseOffset._params.__get__)
316-
return BaseOffset._params.__get__(self)
317-
318-
@cache_readonly
319-
def freqstr(self):
320-
# TODO: see if we can just write cache_readonly(BaseOffset.freqstr.__get__)
321-
return BaseOffset.freqstr.__get__(self)
322-
323-
324-
class SingleConstructorOffset(SingleConstructorMixin, BaseOffset):
325-
@classmethod
326-
def _from_name(cls, suffix=None):
327-
# default _from_name calls cls with no args
328-
if suffix:
329-
raise ValueError(f"Bad freq suffix {suffix}")
330-
return cls()
331-
332302

333303
class BusinessDay(BusinessMixin, SingleConstructorOffset):
334304
"""
@@ -441,7 +411,7 @@ def is_on_offset(self, dt: datetime) -> bool:
441411
return dt.weekday() < 5
442412

443413

444-
class BusinessHour(SingleConstructorMixin, liboffsets.BusinessHourMixin):
414+
class BusinessHour(liboffsets.BusinessHourMixin):
445415
"""
446416
DateOffset subclass representing possibly n business hours.
447417
"""
@@ -1304,7 +1274,7 @@ def _from_name(cls, suffix=None):
13041274
return cls(weekday=weekday)
13051275

13061276

1307-
class WeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin):
1277+
class WeekOfMonth(liboffsets.WeekOfMonthMixin):
13081278
"""
13091279
Describes monthly dates like "the Tuesday of the 2nd week of each month".
13101280
@@ -1365,7 +1335,7 @@ def _from_name(cls, suffix=None):
13651335
return cls(week=week, weekday=weekday)
13661336

13671337

1368-
class LastWeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin):
1338+
class LastWeekOfMonth(liboffsets.WeekOfMonthMixin):
13691339
"""
13701340
Describes monthly dates in last week of month like "the last Tuesday of
13711341
each month".
@@ -1427,7 +1397,7 @@ def _from_name(cls, suffix=None):
14271397
# Quarter-Based Offset Classes
14281398

14291399

1430-
class QuarterOffset(SingleConstructorMixin, liboffsets.QuarterOffset):
1400+
class QuarterOffset(liboffsets.QuarterOffset):
14311401
"""
14321402
Quarter representation.
14331403
"""
@@ -1536,7 +1506,7 @@ class YearBegin(liboffsets.YearOffset):
15361506
# Special Offset Classes
15371507

15381508

1539-
class FY5253Mixin(BaseOffset):
1509+
class FY5253Mixin(SingleConstructorOffset):
15401510
def __init__(
15411511
self, n=1, normalize=False, weekday=0, startingMonth=1, variation="nearest"
15421512
):
@@ -1579,7 +1549,7 @@ def get_rule_code_suffix(self) -> str:
15791549
return f"{prefix}-{month}-{weekday}"
15801550

15811551

1582-
class FY5253(SingleConstructorMixin, FY5253Mixin):
1552+
class FY5253(FY5253Mixin):
15831553
"""
15841554
Describes 52-53 week fiscal year. This is also known as a 4-4-5 calendar.
15851555
@@ -1747,7 +1717,7 @@ def _from_name(cls, *args):
17471717
return cls(**cls._parse_suffix(*args))
17481718

17491719

1750-
class FY5253Quarter(SingleConstructorMixin, FY5253Mixin):
1720+
class FY5253Quarter(FY5253Mixin):
17511721
"""
17521722
DateOffset increments between business quarter dates
17531723
for 52-53 week fiscal year (also known as a 4-4-5 calendar).

0 commit comments

Comments
 (0)