25
25
DtypeObj ,
26
26
npt ,
27
27
)
28
- from pandas .util ._decorators import doc
28
+ from pandas .util ._decorators import (
29
+ cache_readonly ,
30
+ doc ,
31
+ )
29
32
from pandas .util ._exceptions import find_stack_level
30
33
31
34
from pandas .core .dtypes .common import (
@@ -159,6 +162,12 @@ class PeriodIndex(DatetimeIndexOpsMixin):
159
162
_engine_type = libindex .PeriodEngine
160
163
_supports_partial_string_indexing = True
161
164
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
+
162
171
# --------------------------------------------------------------------
163
172
# methods that dispatch to array and wrap result in Index
164
173
# These are defined here instead of via inherit_names for mypy
@@ -446,39 +455,25 @@ def get_loc(self, key, method=None, tolerance=None):
446
455
# TODO: pass if method is not None, like DTI does?
447
456
raise KeyError (key ) from err
448
457
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
451
460
# through _get_string_slice
452
- key = Period ( parsed , freq = self .freq )
461
+ key = self ._cast_partial_indexing_scalar ( key )
453
462
loc = self .get_loc (key , method = method , tolerance = tolerance )
454
463
# Recursing instead of falling through matters for the exception
455
464
# message in test_get_loc3 (though not clear if that really matters)
456
465
return loc
457
466
elif method is None :
458
467
raise KeyError (key )
459
468
else :
460
- key = Period ( parsed , freq = self .freq )
469
+ key = self ._cast_partial_indexing_scalar ( parsed )
461
470
462
471
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
+
476
474
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
+
482
477
else :
483
478
# in particular integer, which Period constructor would cast to string
484
479
raise KeyError (key )
@@ -488,22 +483,42 @@ def get_loc(self, key, method=None, tolerance=None):
488
483
except KeyError as err :
489
484
raise KeyError (orig_key ) from err
490
485
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
+
491
511
@doc (DatetimeIndexOpsMixin ._maybe_cast_slice_bound )
492
512
def _maybe_cast_slice_bound (self , label , side : str , kind = lib .no_default ):
493
513
if isinstance (label , datetime ):
494
- label = Period ( label , freq = self .freq )
514
+ label = self ._cast_partial_indexing_scalar ( label )
495
515
496
516
return super ()._maybe_cast_slice_bound (label , side , kind = kind )
497
517
498
518
def _parsed_string_to_bounds (self , reso : Resolution , parsed : datetime ):
499
519
iv = Period (parsed , freq = reso .attr_abbrev )
500
520
return (iv .asfreq (self .freq , how = "start" ), iv .asfreq (self .freq , how = "end" ))
501
521
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
-
507
522
508
523
def period_range (
509
524
start = None , end = None , periods : int | None = None , freq = None , name = None
0 commit comments