From 1f6b509bb548f4efd035c263c6365273775f77b9 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Fri, 22 May 2020 14:38:26 -0700 Subject: [PATCH] REF: make SingleConstructorOffset a cdef class --- pandas/_libs/tslibs/offsets.pyx | 42 +++++++++++++++--------------- pandas/tseries/offsets.py | 46 ++++++--------------------------- 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 4fd70843d2c42..9eefad19709c6 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -18,6 +18,8 @@ cimport numpy as cnp from numpy cimport int64_t cnp.import_array() +# TODO: formalize having _libs.properties "above" tslibs in the dependency structure +from pandas._libs.properties import cache_readonly from pandas._libs.tslibs cimport util from pandas._libs.tslibs.util cimport is_integer_object, is_datetime64_object @@ -447,7 +449,7 @@ cdef class BaseOffset: def __hash__(self): return hash(self._params) - @property + @cache_readonly def _params(self): """ Returns a tuple containing all of the attributes needed to evaluate @@ -583,7 +585,7 @@ cdef class BaseOffset: def rule_code(self) -> str: return self._prefix - @property + @cache_readonly def freqstr(self) -> str: try: code = self.rule_code @@ -778,7 +780,19 @@ cdef class BaseOffset: return self.n == 1 -cdef class Tick(BaseOffset): +cdef class SingleConstructorOffset(BaseOffset): + @classmethod + def _from_name(cls, suffix=None): + # default _from_name calls cls with no args + if suffix: + raise ValueError(f"Bad freq suffix {suffix}") + return cls() + + +# --------------------------------------------------------------------- +# Tick Offsets + +cdef class Tick(SingleConstructorOffset): # ensure that reversed-ops with numpy scalars return NotImplemented __array_priority__ = 1000 _adjust_dst = False @@ -796,13 +810,6 @@ cdef class Tick(BaseOffset): "Tick offset with `normalize=True` are not allowed." ) - @classmethod - def _from_name(cls, suffix=None): - # default _from_name calls cls with no args - if suffix: - raise ValueError(f"Bad freq suffix {suffix}") - return cls() - def _repr_attrs(self) -> str: # Since cdef classes have no __dict__, we need to override return "" @@ -981,7 +988,7 @@ def delta_to_tick(delta: timedelta) -> Tick: # -------------------------------------------------------------------- -class BusinessMixin(BaseOffset): +class BusinessMixin(SingleConstructorOffset): """ Mixin to business types to provide related functions. """ @@ -1060,13 +1067,6 @@ class BusinessHourMixin(BusinessMixin): object.__setattr__(self, "start", start) object.__setattr__(self, "end", end) - @classmethod - def _from_name(cls, suffix=None): - # default _from_name calls cls with no args - if suffix: - raise ValueError(f"Bad freq suffix {suffix}") - return cls() - def _repr_attrs(self) -> str: out = super()._repr_attrs() hours = ",".join( @@ -1128,7 +1128,7 @@ class CustomMixin: object.__setattr__(self, "calendar", calendar) -class WeekOfMonthMixin(BaseOffset): +class WeekOfMonthMixin(SingleConstructorOffset): """ Mixin for methods common to WeekOfMonth and LastWeekOfMonth. """ @@ -1169,7 +1169,7 @@ class WeekOfMonthMixin(BaseOffset): # ---------------------------------------------------------------------- -cdef class YearOffset(BaseOffset): +cdef class YearOffset(SingleConstructorOffset): """ DateOffset that just needs a month. """ @@ -1230,7 +1230,7 @@ cdef class YearOffset(BaseOffset): return type(dtindex)._simple_new(shifted, dtype=dtindex.dtype) -cdef class QuarterOffset(BaseOffset): +cdef class QuarterOffset(SingleConstructorOffset): _attributes = frozenset(["n", "normalize", "startingMonth"]) # TODO: Consider combining QuarterOffset and YearOffset __init__ at some # point. Also apply_index, is_on_offset, rule_code if diff --git a/pandas/tseries/offsets.py b/pandas/tseries/offsets.py index a1193ad2092e0..291e1c48b3fab 100644 --- a/pandas/tseries/offsets.py +++ b/pandas/tseries/offsets.py @@ -25,6 +25,7 @@ Minute, Nano, Second, + SingleConstructorOffset, Tick, apply_index_wraps, apply_wraps, @@ -298,37 +299,6 @@ def is_on_offset(self, dt): # TODO, see #1395 return True - @cache_readonly - def _params(self): - # TODO: see if we can just write cache_readonly(BaseOffset._params.__get__) - return BaseOffset._params.__get__(self) - - @cache_readonly - def freqstr(self): - # TODO: see if we can just write cache_readonly(BaseOffset.freqstr.__get__) - return BaseOffset.freqstr.__get__(self) - - -class SingleConstructorMixin: - @cache_readonly - def _params(self): - # TODO: see if we can just write cache_readonly(BaseOffset._params.__get__) - return BaseOffset._params.__get__(self) - - @cache_readonly - def freqstr(self): - # TODO: see if we can just write cache_readonly(BaseOffset.freqstr.__get__) - return BaseOffset.freqstr.__get__(self) - - -class SingleConstructorOffset(SingleConstructorMixin, BaseOffset): - @classmethod - def _from_name(cls, suffix=None): - # default _from_name calls cls with no args - if suffix: - raise ValueError(f"Bad freq suffix {suffix}") - return cls() - class BusinessDay(BusinessMixin, SingleConstructorOffset): """ @@ -441,7 +411,7 @@ def is_on_offset(self, dt: datetime) -> bool: return dt.weekday() < 5 -class BusinessHour(SingleConstructorMixin, liboffsets.BusinessHourMixin): +class BusinessHour(liboffsets.BusinessHourMixin): """ DateOffset subclass representing possibly n business hours. """ @@ -1320,7 +1290,7 @@ def _from_name(cls, suffix=None): return cls(weekday=weekday) -class WeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin): +class WeekOfMonth(liboffsets.WeekOfMonthMixin): """ Describes monthly dates like "the Tuesday of the 2nd week of each month". @@ -1381,7 +1351,7 @@ def _from_name(cls, suffix=None): return cls(week=week, weekday=weekday) -class LastWeekOfMonth(SingleConstructorMixin, liboffsets.WeekOfMonthMixin): +class LastWeekOfMonth(liboffsets.WeekOfMonthMixin): """ Describes monthly dates in last week of month like "the last Tuesday of each month". @@ -1443,7 +1413,7 @@ def _from_name(cls, suffix=None): # Quarter-Based Offset Classes -class QuarterOffset(SingleConstructorMixin, liboffsets.QuarterOffset): +class QuarterOffset(liboffsets.QuarterOffset): """ Quarter representation. """ @@ -1552,7 +1522,7 @@ class YearBegin(liboffsets.YearOffset): # Special Offset Classes -class FY5253Mixin(BaseOffset): +class FY5253Mixin(SingleConstructorOffset): def __init__( self, n=1, normalize=False, weekday=0, startingMonth=1, variation="nearest" ): @@ -1595,7 +1565,7 @@ def get_rule_code_suffix(self) -> str: return f"{prefix}-{month}-{weekday}" -class FY5253(SingleConstructorMixin, FY5253Mixin): +class FY5253(FY5253Mixin): """ Describes 52-53 week fiscal year. This is also known as a 4-4-5 calendar. @@ -1763,7 +1733,7 @@ def _from_name(cls, *args): return cls(**cls._parse_suffix(*args)) -class FY5253Quarter(SingleConstructorMixin, FY5253Mixin): +class FY5253Quarter(FY5253Mixin): """ DateOffset increments between business quarter dates for 52-53 week fiscal year (also known as a 4-4-5 calendar).