Skip to content

Commit 91241ae

Browse files
authored
REF: mix PeriodPseudoDtype into PeriodDtype (pandas-dev#34590)
1 parent ff75954 commit 91241ae

File tree

9 files changed

+38
-28
lines changed

9 files changed

+38
-28
lines changed

pandas/_libs/tslibs/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
__all__ = [
2+
"dtypes",
23
"localize_pydatetime",
34
"NaT",
45
"NaTType",
@@ -17,7 +18,7 @@
1718
"to_offset",
1819
]
1920

20-
21+
from . import dtypes # type: ignore
2122
from .conversion import localize_pydatetime
2223
from .nattype import NaT, NaTType, iNaT, is_null_datetimelike, nat_strings
2324
from .np_datetime import OutOfBoundsDatetime

pandas/_libs/tslibs/dtypes.pxd

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ cdef enum PeriodDtypeCode:
5050
U = 11000 # Microsecondly
5151
N = 12000 # Nanosecondly
5252

53+
UNDEFINED = -10_000
5354

54-
cdef class PeriodPseudoDtype:
55+
56+
cdef class PeriodDtypeBase:
5557
cdef readonly:
5658
PeriodDtypeCode dtype_code

pandas/_libs/tslibs/dtypes.pyx

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# originals
33

44

5-
cdef class PeriodPseudoDtype:
5+
cdef class PeriodDtypeBase:
66
"""
77
Similar to an actual dtype, this contains all of the information
88
describing a PeriodDtype in an integer code.
@@ -14,9 +14,9 @@ cdef class PeriodPseudoDtype:
1414
self.dtype_code = code
1515

1616
def __eq__(self, other):
17-
if not isinstance(other, PeriodPseudoDtype):
17+
if not isinstance(other, PeriodDtypeBase):
1818
return False
19-
if not isinstance(self, PeriodPseudoDtype):
19+
if not isinstance(self, PeriodDtypeBase):
2020
# cython semantics, this is a reversed op
2121
return False
2222
return self.dtype_code == other.dtype_code

pandas/_libs/tslibs/offsets.pyx

+1-3
Original file line numberDiff line numberDiff line change
@@ -2529,12 +2529,10 @@ cdef class Week(SingleConstructorOffset):
25292529
-------
25302530
result : DatetimeIndex
25312531
"""
2532-
from .frequencies import get_freq_code # TODO: avoid circular import
2533-
25342532
i8other = dtindex.asi8
25352533
off = (i8other % DAY_NANOS).view("timedelta64[ns]")
25362534

2537-
base, mult = get_freq_code(self.freqstr)
2535+
base = self._period_dtype_code
25382536
base_period = dtindex.to_period(base)
25392537

25402538
if self.n > 0:

pandas/_libs/tslibs/period.pyx

+4-4
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ from pandas._libs.tslibs.ccalendar cimport (
5656
)
5757
from pandas._libs.tslibs.ccalendar cimport c_MONTH_NUMBERS
5858

59-
from pandas._libs.tslibs.dtypes cimport PeriodPseudoDtype
59+
from pandas._libs.tslibs.dtypes cimport PeriodDtypeBase
6060

6161
from pandas._libs.tslibs.frequencies cimport (
6262
attrname_to_abbrevs,
@@ -1514,7 +1514,7 @@ cdef class _Period:
15141514

15151515
cdef readonly:
15161516
int64_t ordinal
1517-
PeriodPseudoDtype _dtype
1517+
PeriodDtypeBase _dtype
15181518
BaseOffset freq
15191519

15201520
def __cinit__(self, int64_t ordinal, BaseOffset freq):
@@ -1523,7 +1523,7 @@ cdef class _Period:
15231523
# Note: this is more performant than PeriodDtype.from_date_offset(freq)
15241524
# because from_date_offset cannot be made a cdef method (until cython
15251525
# supported cdef classmethods)
1526-
self._dtype = PeriodPseudoDtype(freq._period_dtype_code)
1526+
self._dtype = PeriodDtypeBase(freq._period_dtype_code)
15271527

15281528
@classmethod
15291529
def _maybe_convert_freq(cls, object freq) -> BaseOffset:
@@ -2460,7 +2460,7 @@ class Period(_Period):
24602460
raise ValueError(msg)
24612461

24622462
if ordinal is None:
2463-
base, mult = get_freq_code(freq)
2463+
base, _ = get_freq_code(freq)
24642464
ordinal = period_ordinal(dt.year, dt.month, dt.day,
24652465
dt.hour, dt.minute, dt.second,
24662466
dt.microsecond, 0, base)

pandas/core/arrays/period.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252

5353
def _field_accessor(name: str, docstring=None):
5454
def f(self):
55-
base, _ = libfrequencies.get_freq_code(self.freq)
55+
base = self.freq._period_dtype_code
5656
result = get_period_field_arr(name, self.asi8, base)
5757
return result
5858

@@ -440,12 +440,12 @@ def to_timestamp(self, freq=None, how="start"):
440440
return (self + self.freq).to_timestamp(how="start") - adjust
441441

442442
if freq is None:
443-
base, mult = libfrequencies.get_freq_code(self.freq)
443+
base = self.freq._period_dtype_code
444444
freq = libfrequencies.get_to_timestamp_base(base)
445445
else:
446446
freq = Period._maybe_convert_freq(freq)
447447

448-
base, mult = libfrequencies.get_freq_code(freq)
448+
base, _ = libfrequencies.get_freq_code(freq)
449449
new_data = self.asfreq(freq, how=how)
450450

451451
new_data = libperiod.periodarr_to_dt64arr(new_data.asi8, base)
@@ -523,14 +523,14 @@ def asfreq(self, freq=None, how: str = "E") -> "PeriodArray":
523523

524524
freq = Period._maybe_convert_freq(freq)
525525

526-
base1, mult1 = libfrequencies.get_freq_code(self.freq)
527-
base2, mult2 = libfrequencies.get_freq_code(freq)
526+
base1 = self.freq._period_dtype_code
527+
base2 = freq._period_dtype_code
528528

529529
asi8 = self.asi8
530-
# mult1 can't be negative or 0
530+
# self.freq.n can't be negative or 0
531531
end = how == "E"
532532
if end:
533-
ordinal = asi8 + mult1 - 1
533+
ordinal = asi8 + self.freq.n - 1
534534
else:
535535
ordinal = asi8
536536

@@ -950,7 +950,7 @@ def dt64arr_to_periodarr(data, freq, tz=None):
950950
if isinstance(data, (ABCIndexClass, ABCSeries)):
951951
data = data._values
952952

953-
base, mult = libfrequencies.get_freq_code(freq)
953+
base = freq._period_dtype_code
954954
return libperiod.dt64arr_to_periodarr(data.view("i8"), base, tz), freq
955955

956956

pandas/core/dtypes/dtypes.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import pytz
2121

2222
from pandas._libs.interval import Interval
23-
from pandas._libs.tslibs import NaT, Period, Timestamp, timezones, to_offset
23+
from pandas._libs.tslibs import NaT, Period, Timestamp, dtypes, timezones, to_offset
2424
from pandas._libs.tslibs.offsets import BaseOffset
2525
from pandas._typing import DtypeObj, Ordered
2626

@@ -848,7 +848,7 @@ def __setstate__(self, state) -> None:
848848

849849

850850
@register_extension_dtype
851-
class PeriodDtype(PandasExtensionDtype):
851+
class PeriodDtype(dtypes.PeriodDtypeBase, PandasExtensionDtype):
852852
"""
853853
An ExtensionDtype for Period data.
854854
@@ -896,7 +896,8 @@ def __new__(cls, freq=None):
896896

897897
elif freq is None:
898898
# empty constructor for pickle compat
899-
u = object.__new__(cls)
899+
# -10_000 corresponds to PeriodDtypeCode.UNDEFINED
900+
u = dtypes.PeriodDtypeBase.__new__(cls, -10_000)
900901
u._freq = None
901902
return u
902903

@@ -906,11 +907,15 @@ def __new__(cls, freq=None):
906907
try:
907908
return cls._cache[freq.freqstr]
908909
except KeyError:
909-
u = object.__new__(cls)
910+
dtype_code = freq._period_dtype_code
911+
u = dtypes.PeriodDtypeBase.__new__(cls, dtype_code)
910912
u._freq = freq
911913
cls._cache[freq.freqstr] = u
912914
return u
913915

916+
def __reduce__(self):
917+
return type(self), (self.freq,)
918+
914919
@property
915920
def freq(self):
916921
"""
@@ -977,7 +982,7 @@ def __eq__(self, other: Any) -> bool:
977982
return isinstance(other, PeriodDtype) and self.freq == other.freq
978983

979984
def __setstate__(self, state):
980-
# for pickle compat. __get_state__ is defined in the
985+
# for pickle compat. __getstate__ is defined in the
981986
# PandasExtensionDtype superclass and uses the public properties to
982987
# pickle -> need to set the settable private ones here (see GH26067)
983988
self._freq = state["freq"]

pandas/plotting/_matplotlib/timeseries.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import numpy as np
77

88
from pandas._libs.tslibs import Period, to_offset
9-
from pandas._libs.tslibs.frequencies import FreqGroup, base_and_stride, get_freq_code
9+
from pandas._libs.tslibs.frequencies import FreqGroup, base_and_stride
1010
from pandas._typing import FrameOrSeriesUnion
1111

1212
from pandas.core.dtypes.generic import (
@@ -213,7 +213,7 @@ def _use_dynamic_x(ax, data: "FrameOrSeriesUnion") -> bool:
213213

214214
# FIXME: hack this for 0.10.1, creating more technical debt...sigh
215215
if isinstance(data.index, ABCDatetimeIndex):
216-
base = get_freq_code(freq)[0]
216+
base = to_offset(freq)._period_dtype_code
217217
x = data.index
218218
if base <= FreqGroup.FR_DAY:
219219
return x[:1].is_normalized

pandas/tests/dtypes/test_dtypes.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,11 @@ def test_pickle(self, dtype):
6767

6868
# force back to the cache
6969
result = tm.round_trip_pickle(dtype)
70-
assert not len(dtype._cache)
70+
if not isinstance(dtype, PeriodDtype):
71+
# Because PeriodDtype has a cython class as a base class,
72+
# it has different pickle semantics, and its cache is re-populated
73+
# on un-pickling.
74+
assert not len(dtype._cache)
7175
assert result == dtype
7276

7377

0 commit comments

Comments
 (0)