diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index 6c2871cd746b8..0e695bf4e57eb 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -102,5 +102,6 @@ cdef enum PeriodDtypeCode: cdef class PeriodDtypeBase: cdef readonly: PeriodDtypeCode _dtype_code + int64_t _n cpdef int _get_to_timestamp_base(self) diff --git a/pandas/_libs/tslibs/dtypes.pyi b/pandas/_libs/tslibs/dtypes.pyi index b872241d79a54..7142df41a7048 100644 --- a/pandas/_libs/tslibs/dtypes.pyi +++ b/pandas/_libs/tslibs/dtypes.pyi @@ -14,9 +14,10 @@ def abbrev_to_npy_unit(abbrev: str) -> int: ... class PeriodDtypeBase: _dtype_code: int # PeriodDtypeCode + _n: int # actually __cinit__ - def __new__(cls, code: int): ... + def __new__(cls, code: int, n: int): ... @property def _freq_group_code(self) -> int: ... @property @@ -24,6 +25,7 @@ class PeriodDtypeBase: def _get_to_timestamp_base(self) -> int: ... @property def _freqstr(self) -> str: ... + def __hash__(self) -> int: ... class FreqGroup(Enum): FR_ANN: int diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index eb24e631e0a36..2bfbcae70b990 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -18,9 +18,11 @@ cdef class PeriodDtypeBase: """ # cdef readonly: # PeriodDtypeCode _dtype_code + # int64_t _n - def __cinit__(self, PeriodDtypeCode code): + def __cinit__(self, PeriodDtypeCode code, int64_t n): self._dtype_code = code + self._n = n def __eq__(self, other): if not isinstance(other, PeriodDtypeBase): @@ -28,7 +30,10 @@ cdef class PeriodDtypeBase: if not isinstance(self, PeriodDtypeBase): # cython semantics, this is a reversed op return False - return self._dtype_code == other._dtype_code + return self._dtype_code == other._dtype_code and self._n == other._n + + def __hash__(self) -> int: + return hash((self._n, self._dtype_code)) @property def _freq_group_code(self) -> int: diff --git a/pandas/_libs/tslibs/period.pyx b/pandas/_libs/tslibs/period.pyx index 93cda2ec49c26..6ec99fc47dba9 100644 --- a/pandas/_libs/tslibs/period.pyx +++ b/pandas/_libs/tslibs/period.pyx @@ -1671,7 +1671,7 @@ cdef class _Period(PeriodMixin): # Note: this is more performant than PeriodDtype.from_date_offset(freq) # because from_date_offset cannot be made a cdef method (until cython # supported cdef classmethods) - self._dtype = PeriodDtypeBase(freq._period_dtype_code) + self._dtype = PeriodDtypeBase(freq._period_dtype_code, freq.n) @classmethod def _maybe_convert_freq(cls, object freq) -> BaseOffset: @@ -1686,7 +1686,7 @@ cdef class _Period(PeriodMixin): """ if isinstance(freq, int): # We already have a dtype code - dtype = PeriodDtypeBase(freq) + dtype = PeriodDtypeBase(freq, 1) freq = dtype._freqstr freq = to_offset(freq) diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 4698aec5d5312..620266602442f 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -862,6 +862,7 @@ class PeriodDtype(PeriodDtypeBase, PandasExtensionDtype): _metadata = ("freq",) _match = re.compile(r"(P|p)eriod\[(?P.+)\]") _cache_dtypes: dict[str_type, PandasExtensionDtype] = {} + __hash__ = PeriodDtypeBase.__hash__ def __new__(cls, freq): """ @@ -879,7 +880,7 @@ def __new__(cls, freq): return cls._cache_dtypes[freq.freqstr] except KeyError: dtype_code = freq._period_dtype_code - u = PeriodDtypeBase.__new__(cls, dtype_code) + u = PeriodDtypeBase.__new__(cls, dtype_code, freq.n) u._freq = freq cls._cache_dtypes[freq.freqstr] = u return u @@ -945,22 +946,11 @@ def name(self) -> str_type: def na_value(self) -> NaTType: return NaT - def __hash__(self) -> int: - # make myself hashable - return hash(str(self)) - def __eq__(self, other: Any) -> bool: if isinstance(other, str): return other in [self.name, self.name.title()] - elif isinstance(other, PeriodDtype): - # For freqs that can be held by a PeriodDtype, this check is - # equivalent to (and much faster than) self.freq == other.freq - sfreq = self._freq - ofreq = other._freq - return sfreq.n == ofreq.n and self._dtype_code == other._dtype_code - - return False + return super().__eq__(other) def __ne__(self, other: Any) -> bool: return not self.__eq__(other) diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index 2fa2b7f54639d..b2be354e99213 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -25,7 +25,6 @@ ) from pandas.core.dtypes.common import is_integer -from pandas.core.dtypes.dtypes import PeriodDtype from pandas.core.dtypes.generic import ABCSeries from pandas.core.dtypes.missing import is_valid_na_for_dtype @@ -52,6 +51,9 @@ Self, npt, ) + + from pandas.core.dtypes.dtypes import PeriodDtype + _index_doc_kwargs = dict(ibase._index_doc_kwargs) _index_doc_kwargs.update({"target_klass": "PeriodIndex or list of Periods"}) _shared_doc_kwargs = { @@ -314,20 +316,7 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: """ Can we compare values of the given dtype to our own? """ - if not isinstance(dtype, PeriodDtype): - return False - # For the subset of DateOffsets that can be a dtype.freq, it - # suffices (and is much faster) to compare the dtype_code rather than - # the freq itself. - # See also: PeriodDtype.__eq__ - freq = dtype.freq - own_freq = self.freq - return ( - freq._period_dtype_code - # error: "BaseOffset" has no attribute "_period_dtype_code" - == own_freq._period_dtype_code # type: ignore[attr-defined] - and freq.n == own_freq.n - ) + return self.dtype == dtype # ------------------------------------------------------------------------ # Index Methods