2525 DtypeObj ,
2626 npt ,
2727)
28- from pandas .util ._decorators import doc
28+ from pandas .util ._decorators import (
29+ cache_readonly ,
30+ doc ,
31+ )
2932from pandas .util ._exceptions import find_stack_level
3033
3134from pandas .core .dtypes .common import (
@@ -159,6 +162,12 @@ class PeriodIndex(DatetimeIndexOpsMixin):
159162 _engine_type = libindex .PeriodEngine
160163 _supports_partial_string_indexing = True
161164
165+ @cache_readonly
166+ # Signature of "_resolution_obj" incompatible with supertype "DatetimeIndexOpsMixin"
167+ def _resolution_obj (self ) -> Resolution : # type: ignore[override]
168+ # for compat with DatetimeIndex
169+ return self .dtype ._resolution_obj
170+
162171 # --------------------------------------------------------------------
163172 # methods that dispatch to array and wrap result in Index
164173 # These are defined here instead of via inherit_names for mypy
@@ -446,39 +455,25 @@ def get_loc(self, key, method=None, tolerance=None):
446455 # TODO: pass if method is not None, like DTI does?
447456 raise KeyError (key ) from err
448457
449- if reso == self .dtype . _resolution_obj :
450- # the reso < self.dtype. _resolution_obj case goes
458+ if reso == self ._resolution_obj :
459+ # the reso < self._resolution_obj case goes
451460 # through _get_string_slice
452- key = Period ( parsed , freq = self .freq )
461+ key = self ._cast_partial_indexing_scalar ( key )
453462 loc = self .get_loc (key , method = method , tolerance = tolerance )
454463 # Recursing instead of falling through matters for the exception
455464 # message in test_get_loc3 (though not clear if that really matters)
456465 return loc
457466 elif method is None :
458467 raise KeyError (key )
459468 else :
460- key = Period ( parsed , freq = self .freq )
469+ key = self ._cast_partial_indexing_scalar ( parsed )
461470
462471 elif isinstance (key , Period ):
463- sfreq = self .freq
464- kfreq = key .freq
465- if not (
466- sfreq .n == kfreq .n
467- # error: "BaseOffset" has no attribute "_period_dtype_code"
468- and sfreq ._period_dtype_code # type: ignore[attr-defined]
469- # error: "BaseOffset" has no attribute "_period_dtype_code"
470- == kfreq ._period_dtype_code # type: ignore[attr-defined]
471- ):
472- # GH#42247 For the subset of DateOffsets that can be Period freqs,
473- # checking these two attributes is sufficient to check equality,
474- # and much more performant than `self.freq == key.freq`
475- raise KeyError (key )
472+ key = self ._maybe_cast_for_get_loc (key )
473+
476474 elif isinstance (key , datetime ):
477- try :
478- key = Period (key , freq = self .freq )
479- except ValueError as err :
480- # we cannot construct the Period
481- raise KeyError (orig_key ) from err
475+ key = self ._cast_partial_indexing_scalar (key )
476+
482477 else :
483478 # in particular integer, which Period constructor would cast to string
484479 raise KeyError (key )
@@ -488,22 +483,42 @@ def get_loc(self, key, method=None, tolerance=None):
488483 except KeyError as err :
489484 raise KeyError (orig_key ) from err
490485
486+ def _maybe_cast_for_get_loc (self , key : Period ) -> Period :
487+ # name is a misnomer, chosen for compat with DatetimeIndex
488+ sfreq = self .freq
489+ kfreq = key .freq
490+ if not (
491+ sfreq .n == kfreq .n
492+ # error: "BaseOffset" has no attribute "_period_dtype_code"
493+ and sfreq ._period_dtype_code # type: ignore[attr-defined]
494+ # error: "BaseOffset" has no attribute "_period_dtype_code"
495+ == kfreq ._period_dtype_code # type: ignore[attr-defined]
496+ ):
497+ # GH#42247 For the subset of DateOffsets that can be Period freqs,
498+ # checking these two attributes is sufficient to check equality,
499+ # and much more performant than `self.freq == key.freq`
500+ raise KeyError (key )
501+ return key
502+
503+ def _cast_partial_indexing_scalar (self , label ):
504+ try :
505+ key = Period (label , freq = self .freq )
506+ except ValueError as err :
507+ # we cannot construct the Period
508+ raise KeyError (label ) from err
509+ return key
510+
491511 @doc (DatetimeIndexOpsMixin ._maybe_cast_slice_bound )
492512 def _maybe_cast_slice_bound (self , label , side : str , kind = lib .no_default ):
493513 if isinstance (label , datetime ):
494- label = Period ( label , freq = self .freq )
514+ label = self ._cast_partial_indexing_scalar ( label )
495515
496516 return super ()._maybe_cast_slice_bound (label , side , kind = kind )
497517
498518 def _parsed_string_to_bounds (self , reso : Resolution , parsed : datetime ):
499519 iv = Period (parsed , freq = reso .attr_abbrev )
500520 return (iv .asfreq (self .freq , how = "start" ), iv .asfreq (self .freq , how = "end" ))
501521
502- def _can_partial_date_slice (self , reso : Resolution ) -> bool :
503- assert isinstance (reso , Resolution ), (type (reso ), reso )
504- # e.g. test_getitem_setitem_periodindex
505- return reso > self .dtype ._resolution_obj
506-
507522
508523def period_range (
509524 start = None , end = None , periods : int | None = None , freq = None , name = None
0 commit comments