diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 35f9f623bf8ef..74efba0acf739 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -376,12 +376,12 @@ Period ^^^^^^ - :meth:`PeriodIndex.map` with ``na_action="ignore"`` now works as expected (:issue:`51644`) - Bug in :class:`PeriodDtype` constructor failing to raise ``TypeError`` when no argument is passed or when ``None`` is passed (:issue:`27388`) +- Bug in :class:`PeriodDtype` constructor incorrectly returning the same ``normalize`` for different :class:`DateOffset` ``freq`` inputs (:issue:`24121`) - Bug in :class:`PeriodDtype` constructor raising ``ValueError`` instead of ``TypeError`` when an invalid type is passed (:issue:`51790`) - Bug in :func:`read_csv` not processing empty strings as a null value, with ``engine="pyarrow"`` (:issue:`52087`) - Bug in :func:`read_csv` returning ``object`` dtype columns instead of ``float64`` dtype columns with ``engine="pyarrow"`` for columns that are all null with ``engine="pyarrow"`` (:issue:`52087`) - Bug in :meth:`arrays.PeriodArray.map` and :meth:`PeriodIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`) - Bug in incorrectly allowing construction of :class:`Period` or :class:`PeriodDtype` with :class:`CustomBusinessDay` freq; use :class:`BusinessDay` instead (:issue:`52534`) -- Plotting ^^^^^^^^ diff --git a/pandas/core/dtypes/dtypes.py b/pandas/core/dtypes/dtypes.py index 6ece1a17a2e0f..a03d5978adfbf 100644 --- a/pandas/core/dtypes/dtypes.py +++ b/pandas/core/dtypes/dtypes.py @@ -899,7 +899,10 @@ class PeriodDtype(PeriodDtypeBase, PandasExtensionDtype): num = 102 _metadata = ("freq",) _match = re.compile(r"(P|p)eriod\[(?P.+)\]") - _cache_dtypes: dict[str_type, PandasExtensionDtype] = {} + # error: Incompatible types in assignment (expression has type + # "Dict[int, PandasExtensionDtype]", base class "PandasExtensionDtype" + # defined the type as "Dict[str, PandasExtensionDtype]") [assignment] + _cache_dtypes: dict[BaseOffset, PeriodDtype] = {} # type: ignore[assignment] # noqa:E501 __hash__ = PeriodDtypeBase.__hash__ _freq: BaseOffset @@ -916,12 +919,12 @@ def __new__(cls, freq): freq = cls._parse_dtype_strict(freq) try: - return cls._cache_dtypes[freq.freqstr] + return cls._cache_dtypes[freq] except KeyError: dtype_code = freq._period_dtype_code u = PeriodDtypeBase.__new__(cls, dtype_code, freq.n) u._freq = freq - cls._cache_dtypes[freq.freqstr] = u + cls._cache_dtypes[freq] = u return u def __reduce__(self): diff --git a/pandas/tests/dtypes/test_dtypes.py b/pandas/tests/dtypes/test_dtypes.py index 745a933f3a780..1cc10d14b904f 100644 --- a/pandas/tests/dtypes/test_dtypes.py +++ b/pandas/tests/dtypes/test_dtypes.py @@ -559,6 +559,14 @@ def test_not_string(self): # though PeriodDtype has object kind, it cannot be string assert not is_string_dtype(PeriodDtype("D")) + def test_perioddtype_caching_dateoffset_normalize(self): + # GH 24121 + per_d = PeriodDtype(pd.offsets.YearEnd(normalize=True)) + assert per_d.freq.normalize + + per_d2 = PeriodDtype(pd.offsets.YearEnd(normalize=False)) + assert not per_d2.freq.normalize + class TestIntervalDtype(Base): @pytest.fixture