From b82adc5ac6bfe2412b1f5f22d451d85cbc82c8d7 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 4 May 2020 14:10:18 -0700 Subject: [PATCH] PERF: make _Tick into a cdef class --- pandas/_libs/tslibs/offsets.pxd | 2 ++ pandas/_libs/tslibs/offsets.pyx | 24 +++++++++++++++++++----- pandas/compat/pickle_compat.py | 7 +++++++ pandas/io/pickle.py | 3 ++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/pandas/_libs/tslibs/offsets.pxd b/pandas/_libs/tslibs/offsets.pxd index 5a553be537e52..e75cd8bdf1baf 100644 --- a/pandas/_libs/tslibs/offsets.pxd +++ b/pandas/_libs/tslibs/offsets.pxd @@ -1 +1,3 @@ cdef to_offset(object obj) +cdef bint is_offset_object(object obj) +cdef bint is_tick_object(object obj) diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx index 3dfaa36888f62..9fbe717fa8c2c 100644 --- a/pandas/_libs/tslibs/offsets.pyx +++ b/pandas/_libs/tslibs/offsets.pyx @@ -86,6 +86,14 @@ for _d in DAYS: # --------------------------------------------------------------------- # Misc Helpers +cdef bint is_offset_object(object obj): + return isinstance(obj, _BaseOffset) + + +cdef bint is_tick_object(object obj): + return isinstance(obj, _Tick) + + cdef to_offset(object obj): """ Wrap pandas.tseries.frequencies.to_offset to keep centralize runtime @@ -608,7 +616,7 @@ class BaseOffset(_BaseOffset): return -self + other -class _Tick: +cdef class _Tick: """ dummy class to mix into tseries.offsets.Tick so that in tslibs.period we can do isinstance checks on _Tick and avoid importing tseries.offsets @@ -618,12 +626,18 @@ class _Tick: __array_priority__ = 1000 def __truediv__(self, other): - result = self.delta.__truediv__(other) + if not isinstance(self, _Tick): + # cython semantics mean the args are sometimes swapped + result = other.delta.__rtruediv__(self) + else: + result = self.delta.__truediv__(other) return _wrap_timedelta_result(result) - def __rtruediv__(self, other): - result = self.delta.__rtruediv__(other) - return _wrap_timedelta_result(result) + def __reduce__(self): + return (type(self), (self.n,)) + + def __setstate__(self, state): + object.__setattr__(self, "n", state["n"]) class BusinessMixin: diff --git a/pandas/compat/pickle_compat.py b/pandas/compat/pickle_compat.py index 3f4acca8bce18..cd2ded874c08c 100644 --- a/pandas/compat/pickle_compat.py +++ b/pandas/compat/pickle_compat.py @@ -9,6 +9,8 @@ from pandas import Index +from pandas.tseries.offsets import Tick + if TYPE_CHECKING: from pandas import Series, DataFrame @@ -38,6 +40,11 @@ def load_reduce(self): return except TypeError: pass + elif args and issubclass(args[0], Tick): + # TypeError: object.__new__(Day) is not safe, use Day.__new__() + cls = args[0] + stack[-1] = cls.__new__(*args) + return raise diff --git a/pandas/io/pickle.py b/pandas/io/pickle.py index 6faebf56a11ab..3b35b54a6dc16 100644 --- a/pandas/io/pickle.py +++ b/pandas/io/pickle.py @@ -173,7 +173,8 @@ def read_pickle( # 3) try pickle_compat with latin-1 encoding upon a UnicodeDecodeError try: - excs_to_catch = (AttributeError, ImportError, ModuleNotFoundError) + excs_to_catch = (AttributeError, ImportError, ModuleNotFoundError, TypeError) + # TypeError for Cython complaints about object.__new__ vs Tick.__new__ try: with warnings.catch_warnings(record=True): # We want to silence any warnings about, e.g. moved modules.