diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 70019030da182..de44c64615a7b 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -231,6 +231,7 @@ ReadBuffer, Renamer, Scalar, + Self, SortKind, StorageOptions, Suffixes, @@ -5008,7 +5009,7 @@ def align( limit: int | None = None, fill_axis: Axis = 0, broadcast_axis: Axis | None = None, - ) -> tuple[DataFrame, NDFrameT]: + ) -> tuple[Self, NDFrameT]: return super().align( other, join=join, diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 5b77bb9651073..816f7e2b30a26 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -72,6 +72,7 @@ RandomState, Renamer, Scalar, + Self, SortKind, StorageOptions, Suffixes, @@ -310,7 +311,7 @@ def _init_mgr( mgr = mgr.astype(dtype=dtype) return mgr - def _as_manager(self: NDFrameT, typ: str, copy: bool_t = True) -> NDFrameT: + def _as_manager(self, typ: str, copy: bool_t = True) -> Self: """ Private helper function to create a DataFrame with specific manager. @@ -399,11 +400,11 @@ def flags(self) -> Flags: @final def set_flags( - self: NDFrameT, + self, *, copy: bool_t = False, allows_duplicate_labels: bool_t | None = None, - ) -> NDFrameT: + ) -> Self: """ Return a new object with updated flags. @@ -470,7 +471,7 @@ def _validate_dtype(cls, dtype) -> DtypeObj | None: # Construction @property - def _constructor(self: NDFrameT) -> Callable[..., NDFrameT]: + def _constructor(self) -> Callable[..., Self]: """ Used when a manipulation result has the same dimensions as the original. @@ -670,12 +671,12 @@ def size(self) -> int: return np.prod(self.shape) # type: ignore[return-value] def set_axis( - self: NDFrameT, + self, labels, *, axis: Axis = 0, copy: bool_t | None = None, - ) -> NDFrameT: + ) -> Self: """ Assign desired index to given axis. @@ -731,9 +732,7 @@ def _set_axis(self, axis: AxisInt, labels: AnyArrayLike | list) -> None: self._clear_item_cache() @final - def swapaxes( - self: NDFrameT, axis1: Axis, axis2: Axis, copy: bool_t | None = None - ) -> NDFrameT: + def swapaxes(self, axis1: Axis, axis2: Axis, copy: bool_t | None = None) -> Self: """ Interchange axes and swap values axes appropriately. @@ -784,7 +783,7 @@ def swapaxes( @final @doc(klass=_shared_doc_kwargs["klass"]) - def droplevel(self: NDFrameT, level: IndexLabel, axis: Axis = 0) -> NDFrameT: + def droplevel(self, level: IndexLabel, axis: Axis = 0) -> Self: """ Return {klass} with requested index / column level(s) removed. @@ -969,7 +968,7 @@ def squeeze(self, axis: Axis | None = None): # Rename def _rename( - self: NDFrameT, + self, mapper: Renamer | None = None, *, index: Renamer | None = None, @@ -979,7 +978,7 @@ def _rename( inplace: bool_t = False, level: Level | None = None, errors: str = "ignore", - ) -> NDFrameT | None: + ) -> Self | None: # called by Series.rename and DataFrame.rename if mapper is None and index is None and columns is None: @@ -1041,7 +1040,7 @@ def _rename( @overload def rename_axis( - self: NDFrameT, + self, mapper: IndexLabel | lib.NoDefault = ..., *, index=..., @@ -1049,7 +1048,7 @@ def rename_axis( axis: Axis = ..., copy: bool_t | None = ..., inplace: Literal[False] = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -1067,7 +1066,7 @@ def rename_axis( @overload def rename_axis( - self: NDFrameT, + self, mapper: IndexLabel | lib.NoDefault = ..., *, index=..., @@ -1075,11 +1074,11 @@ def rename_axis( axis: Axis = ..., copy: bool_t | None = ..., inplace: bool_t = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... def rename_axis( - self: NDFrameT, + self, mapper: IndexLabel | lib.NoDefault = lib.no_default, *, index=lib.no_default, @@ -1087,7 +1086,7 @@ def rename_axis( axis: Axis = 0, copy: bool_t | None = None, inplace: bool_t = False, - ) -> NDFrameT | None: + ) -> Self | None: """ Set the name of the axis for the index or columns. @@ -1418,7 +1417,7 @@ def equals(self, other: object) -> bool_t: # Unary Methods @final - def __neg__(self: NDFrameT) -> NDFrameT: + def __neg__(self) -> Self: def blk_func(values: ArrayLike): if is_bool_dtype(values.dtype): # error: Argument 1 to "inv" has incompatible type "Union @@ -1436,7 +1435,7 @@ def blk_func(values: ArrayLike): return res.__finalize__(self, method="__neg__") @final - def __pos__(self: NDFrameT) -> NDFrameT: + def __pos__(self) -> Self: def blk_func(values: ArrayLike): if is_bool_dtype(values.dtype): return values.copy() @@ -1451,7 +1450,7 @@ def blk_func(values: ArrayLike): return res.__finalize__(self, method="__pos__") @final - def __invert__(self: NDFrameT) -> NDFrameT: + def __invert__(self) -> Self: if not self.size: # inv fails with 0 len return self.copy(deep=False) @@ -1516,7 +1515,7 @@ def bool(self) -> bool_t: return True @final - def abs(self: NDFrameT) -> NDFrameT: + def abs(self) -> Self: """ Return a Series/DataFrame with absolute numeric value of each element. @@ -1587,11 +1586,11 @@ def abs(self: NDFrameT) -> NDFrameT: return self._constructor(res_mgr).__finalize__(self, name="abs") @final - def __abs__(self: NDFrameT) -> NDFrameT: + def __abs__(self) -> Self: return self.abs() @final - def __round__(self: NDFrameT, decimals: int = 0) -> NDFrameT: + def __round__(self, decimals: int = 0) -> Self: return self.round(decimals).__finalize__(self, method="__round__") # ------------------------------------------------------------------------- @@ -3821,7 +3820,7 @@ def _clear_item_cache(self) -> None: # Indexing Methods @final - def take(self: NDFrameT, indices, axis: Axis = 0, **kwargs) -> NDFrameT: + def take(self, indices, axis: Axis = 0, **kwargs) -> Self: """ Return the elements in the given *positional* indices along an axis. @@ -3898,6 +3897,19 @@ class max_speed nv.validate_take((), kwargs) + return self._take(indices, axis) + + def _take( + self, + indices, + axis: Axis = 0, + convert_indices: bool_t = True, + ) -> Self: + """ + Internal version of the `take` allowing specification of additional args. + + See the docstring of `take` for full explanation of the parameters. + """ if not isinstance(indices, slice): indices = np.asarray(indices, dtype=np.intp) if ( @@ -3935,7 +3947,7 @@ class max_speed return self._constructor(new_data).__finalize__(self, method="take") @final - def _take_with_is_copy(self: NDFrameT, indices, axis: Axis = 0) -> NDFrameT: + def _take_with_is_copy(self, indices, axis: Axis = 0) -> Self: """ Internal version of the `take` method that sets the `_is_copy` attribute to keep track of the parent dataframe (using in indexing @@ -3953,12 +3965,12 @@ def _take_with_is_copy(self: NDFrameT, indices, axis: Axis = 0) -> NDFrameT: @final def xs( - self: NDFrameT, + self, key: IndexLabel, axis: Axis = 0, level: IndexLabel = None, drop_level: bool_t = True, - ) -> NDFrameT: + ) -> Self: """ Return cross-section from the Series/DataFrame. @@ -4133,7 +4145,7 @@ class animal locomotion def __getitem__(self, item): raise AbstractMethodError(self) - def _slice(self: NDFrameT, slobj: slice, axis: Axis = 0) -> NDFrameT: + def _slice(self, slobj: slice, axis: Axis = 0) -> Self: """ Construct a slice of this container. @@ -4363,13 +4375,13 @@ def _is_view(self) -> bool_t: @final def reindex_like( - self: NDFrameT, + self, other, method: Literal["backfill", "bfill", "pad", "ffill", "nearest"] | None = None, copy: bool_t | None = None, limit=None, tolerance=None, - ) -> NDFrameT: + ) -> Self: """ Return an object with matching indices as other object. @@ -4489,7 +4501,7 @@ def drop( @overload def drop( - self: NDFrameT, + self, labels: IndexLabel = ..., *, axis: Axis = ..., @@ -4498,12 +4510,12 @@ def drop( level: Level | None = ..., inplace: Literal[False] = ..., errors: IgnoreRaise = ..., - ) -> NDFrameT: + ) -> Self: ... @overload def drop( - self: NDFrameT, + self, labels: IndexLabel = ..., *, axis: Axis = ..., @@ -4512,11 +4524,11 @@ def drop( level: Level | None = ..., inplace: bool_t = ..., errors: IgnoreRaise = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... def drop( - self: NDFrameT, + self, labels: IndexLabel = None, *, axis: Axis = 0, @@ -4525,7 +4537,7 @@ def drop( level: Level | None = None, inplace: bool_t = False, errors: IgnoreRaise = "raise", - ) -> NDFrameT | None: + ) -> Self | None: inplace = validate_bool_kwarg(inplace, "inplace") if labels is not None: @@ -4556,13 +4568,13 @@ def drop( @final def _drop_axis( - self: NDFrameT, + self, labels, axis, level=None, errors: IgnoreRaise = "raise", only_slice: bool_t = False, - ) -> NDFrameT: + ) -> Self: """ Drop labels from specified axis. Used in the ``drop`` method internally. @@ -4660,7 +4672,7 @@ def _update_inplace(self, result, verify_is_copy: bool_t = True) -> None: self._maybe_update_cacher(verify_is_copy=verify_is_copy, inplace=True) @final - def add_prefix(self: NDFrameT, prefix: str, axis: Axis | None = None) -> NDFrameT: + def add_prefix(self, prefix: str, axis: Axis | None = None) -> Self: """ Prefix labels with string `prefix`. @@ -4726,15 +4738,15 @@ def add_prefix(self: NDFrameT, prefix: str, axis: Axis | None = None) -> NDFrame mapper = {axis_name: f} - # error: Incompatible return value type (got "Optional[NDFrameT]", - # expected "NDFrameT") + # error: Incompatible return value type (got "Optional[Self]", + # expected "Self") # error: Argument 1 to "rename" of "NDFrame" has incompatible type # "**Dict[str, partial[str]]"; expected "Union[str, int, None]" # error: Keywords must be strings return self._rename(**mapper) # type: ignore[return-value, arg-type, misc] @final - def add_suffix(self: NDFrameT, suffix: str, axis: Axis | None = None) -> NDFrameT: + def add_suffix(self, suffix: str, axis: Axis | None = None) -> Self: """ Suffix labels with string `suffix`. @@ -4799,8 +4811,8 @@ def add_suffix(self: NDFrameT, suffix: str, axis: Axis | None = None) -> NDFrame axis_name = self._get_axis_name(axis) mapper = {axis_name: f} - # error: Incompatible return value type (got "Optional[NDFrameT]", - # expected "NDFrameT") + # error: Incompatible return value type (got "Optional[Self]", + # expected "Self") # error: Argument 1 to "rename" of "NDFrame" has incompatible type # "**Dict[str, partial[str]]"; expected "Union[str, int, None]" # error: Keywords must be strings @@ -4808,7 +4820,7 @@ def add_suffix(self: NDFrameT, suffix: str, axis: Axis | None = None) -> NDFrame @overload def sort_values( - self: NDFrameT, + self, *, axis: Axis = ..., ascending: bool_t | Sequence[bool_t] = ..., @@ -4817,7 +4829,7 @@ def sort_values( na_position: str = ..., ignore_index: bool_t = ..., key: ValueKeyFunc = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -4836,7 +4848,7 @@ def sort_values( @overload def sort_values( - self: NDFrameT, + self, *, axis: Axis = ..., ascending: bool_t | Sequence[bool_t] = ..., @@ -4845,11 +4857,11 @@ def sort_values( na_position: str = ..., ignore_index: bool_t = ..., key: ValueKeyFunc = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... def sort_values( - self: NDFrameT, + self, *, axis: Axis = 0, ascending: bool_t | Sequence[bool_t] = True, @@ -4858,7 +4870,7 @@ def sort_values( na_position: str = "last", ignore_index: bool_t = False, key: ValueKeyFunc = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Sort by the values along either axis. @@ -5020,7 +5032,7 @@ def sort_index( @overload def sort_index( - self: NDFrameT, + self, *, axis: Axis = ..., level: IndexLabel = ..., @@ -5031,12 +5043,12 @@ def sort_index( sort_remaining: bool_t = ..., ignore_index: bool_t = ..., key: IndexKeyFunc = ..., - ) -> NDFrameT: + ) -> Self: ... @overload def sort_index( - self: NDFrameT, + self, *, axis: Axis = ..., level: IndexLabel = ..., @@ -5047,11 +5059,11 @@ def sort_index( sort_remaining: bool_t = ..., ignore_index: bool_t = ..., key: IndexKeyFunc = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... def sort_index( - self: NDFrameT, + self, *, axis: Axis = 0, level: IndexLabel = None, @@ -5062,7 +5074,7 @@ def sort_index( sort_remaining: bool_t = True, ignore_index: bool_t = False, key: IndexKeyFunc = None, - ) -> NDFrameT | None: + ) -> Self | None: inplace = validate_bool_kwarg(inplace, "inplace") axis = self._get_axis_number(axis) ascending = validate_ascending(ascending) @@ -5108,7 +5120,7 @@ def sort_index( optional_reindex="", ) def reindex( - self: NDFrameT, + self, labels=None, index=None, columns=None, @@ -5119,7 +5131,7 @@ def reindex( fill_value: Scalar | None = np.nan, limit: int | None = None, tolerance=None, - ) -> NDFrameT: + ) -> Self: """ Conform {klass} to new index with optional filling logic. @@ -5362,8 +5374,8 @@ def reindex( ).__finalize__(self, method="reindex") def _reindex_axes( - self: NDFrameT, axes, level, limit, tolerance, method, fill_value, copy - ) -> NDFrameT: + self, axes, level, limit, tolerance, method, fill_value, copy + ) -> Self: """Perform the reindex for all the axes.""" obj = self for a in self._AXIS_ORDERS: @@ -5407,12 +5419,12 @@ def _reindex_multi(self, axes, copy, fill_value): @final def _reindex_with_indexers( - self: NDFrameT, + self, reindexers, fill_value=None, copy: bool_t | None = False, allow_dups: bool_t = False, - ) -> NDFrameT: + ) -> Self: """allow_dups indicates an internal call here""" # reindex doing multiple operations on different axes if indicated new_data = self._mgr @@ -5451,12 +5463,12 @@ def _reindex_with_indexers( return self._constructor(new_data).__finalize__(self) def filter( - self: NDFrameT, + self, items=None, like: str | None = None, regex: str | None = None, axis: Axis | None = None, - ) -> NDFrameT: + ) -> Self: """ Subset the dataframe rows or columns according to the specified index labels. @@ -5557,7 +5569,7 @@ def f(x) -> bool_t: raise TypeError("Must pass either `items`, `like`, or `regex`") @final - def head(self: NDFrameT, n: int = 5) -> NDFrameT: + def head(self, n: int = 5) -> Self: """ Return the first `n` rows. @@ -5632,7 +5644,7 @@ def head(self: NDFrameT, n: int = 5) -> NDFrameT: return self.iloc[:n] @final - def tail(self: NDFrameT, n: int = 5) -> NDFrameT: + def tail(self, n: int = 5) -> Self: """ Return the last `n` rows. @@ -5710,7 +5722,7 @@ def tail(self: NDFrameT, n: int = 5) -> NDFrameT: @final def sample( - self: NDFrameT, + self, n: int | None = None, frac: float | None = None, replace: bool_t = False, @@ -5718,7 +5730,7 @@ def sample( random_state: RandomState | None = None, axis: Axis | None = None, ignore_index: bool_t = False, - ) -> NDFrameT: + ) -> Self: """ Return a random sample of items from an axis of object. @@ -5974,9 +5986,7 @@ def pipe( # Attribute access @final - def __finalize__( - self: NDFrameT, other, method: str | None = None, **kwargs - ) -> NDFrameT: + def __finalize__(self, other, method: str | None = None, **kwargs) -> Self: """ Propagate metadata from other to self. @@ -6153,7 +6163,7 @@ def _check_inplace_setting(self, value) -> bool_t: return True @final - def _get_numeric_data(self: NDFrameT) -> NDFrameT: + def _get_numeric_data(self) -> Self: return self._constructor(self._mgr.get_numeric_data()).__finalize__(self) @final @@ -6204,8 +6214,8 @@ def dtypes(self): return self._constructor_sliced(data, index=self._info_axis, dtype=np.object_) def astype( - self: NDFrameT, dtype, copy: bool_t | None = None, errors: IgnoreRaise = "raise" - ) -> NDFrameT: + self, dtype, copy: bool_t | None = None, errors: IgnoreRaise = "raise" + ) -> Self: """ Cast a pandas object to a specified dtype ``dtype``. @@ -6377,15 +6387,15 @@ def astype( result = concat(results, axis=1, copy=False) # GH#40810 retain subclass # error: Incompatible types in assignment - # (expression has type "NDFrameT", variable has type "DataFrame") + # (expression has type "Self", variable has type "DataFrame") result = self._constructor(result) # type: ignore[assignment] result.columns = self.columns result = result.__finalize__(self, method="astype") # https://github.com/python/mypy/issues/8354 - return cast(NDFrameT, result) + return cast(Self, result) @final - def copy(self: NDFrameT, deep: bool_t | None = True) -> NDFrameT: + def copy(self, deep: bool_t | None = True) -> Self: """ Make a copy of this object's indices and data. @@ -6499,11 +6509,11 @@ def copy(self: NDFrameT, deep: bool_t | None = True) -> NDFrameT: return self._constructor(data).__finalize__(self, method="copy") @final - def __copy__(self: NDFrameT, deep: bool_t = True) -> NDFrameT: + def __copy__(self, deep: bool_t = True) -> Self: return self.copy(deep=deep) @final - def __deepcopy__(self: NDFrameT, memo=None) -> NDFrameT: + def __deepcopy__(self, memo=None) -> Self: """ Parameters ---------- @@ -6513,7 +6523,7 @@ def __deepcopy__(self: NDFrameT, memo=None) -> NDFrameT: return self.copy(deep=True) @final - def infer_objects(self: NDFrameT, copy: bool_t | None = None) -> NDFrameT: + def infer_objects(self, copy: bool_t | None = None) -> Self: """ Attempt to infer better dtypes for object columns. @@ -6562,14 +6572,14 @@ def infer_objects(self: NDFrameT, copy: bool_t | None = None) -> NDFrameT: @final def convert_dtypes( - self: NDFrameT, + self, infer_objects: bool_t = True, convert_string: bool_t = True, convert_integer: bool_t = True, convert_boolean: bool_t = True, convert_floating: bool_t = True, dtype_backend: DtypeBackend = "numpy_nullable", - ) -> NDFrameT: + ) -> Self: """ Convert columns to the best possible dtypes using dtypes supporting ``pd.NA``. @@ -6737,7 +6747,7 @@ def convert_dtypes( result = cons(result) result = result.__finalize__(self, method="convert_dtypes") # https://github.com/python/mypy/issues/8354 - return cast(NDFrameT, result) + return cast(Self, result) else: return self.copy(deep=None) @@ -6746,7 +6756,7 @@ def convert_dtypes( @overload def fillna( - self: NDFrameT, + self, value: Hashable | Mapping | Series | DataFrame = ..., *, method: FillnaOptions | None = ..., @@ -6754,7 +6764,7 @@ def fillna( inplace: Literal[False] = ..., limit: int | None = ..., downcast: dict | None = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -6772,7 +6782,7 @@ def fillna( @overload def fillna( - self: NDFrameT, + self, value: Hashable | Mapping | Series | DataFrame = ..., *, method: FillnaOptions | None = ..., @@ -6780,12 +6790,12 @@ def fillna( inplace: bool_t = ..., limit: int | None = ..., downcast: dict | None = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc(**_shared_doc_kwargs) def fillna( - self: NDFrameT, + self, value: Hashable | Mapping | Series | DataFrame = None, *, method: FillnaOptions | None = None, @@ -6793,7 +6803,7 @@ def fillna( inplace: bool_t = False, limit: int | None = None, downcast: dict | None = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Fill NA/NaN values using the specified method. @@ -7047,13 +7057,13 @@ def fillna( @overload def ffill( - self: NDFrameT, + self, *, axis: None | Axis = ..., inplace: Literal[False] = ..., limit: None | int = ..., downcast: dict | None = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -7069,24 +7079,24 @@ def ffill( @overload def ffill( - self: NDFrameT, + self, *, axis: None | Axis = ..., inplace: bool_t = ..., limit: None | int = ..., downcast: dict | None = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc(klass=_shared_doc_kwargs["klass"]) def ffill( - self: NDFrameT, + self, *, axis: None | Axis = None, inplace: bool_t = False, limit: None | int = None, downcast: dict | None = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Synonym for :meth:`DataFrame.fillna` with ``method='ffill'``. @@ -7101,13 +7111,13 @@ def ffill( @doc(klass=_shared_doc_kwargs["klass"]) def pad( - self: NDFrameT, + self, *, axis: None | Axis = None, inplace: bool_t = False, limit: None | int = None, downcast: dict | None = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Synonym for :meth:`DataFrame.fillna` with ``method='ffill'``. @@ -7130,13 +7140,13 @@ def pad( @overload def bfill( - self: NDFrameT, + self, *, axis: None | Axis = ..., inplace: Literal[False] = ..., limit: None | int = ..., downcast: dict | None = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -7152,24 +7162,24 @@ def bfill( @overload def bfill( - self: NDFrameT, + self, *, axis: None | Axis = ..., inplace: bool_t = ..., limit: None | int = ..., downcast: dict | None = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc(klass=_shared_doc_kwargs["klass"]) def bfill( - self: NDFrameT, + self, *, axis: None | Axis = None, inplace: bool_t = False, limit: None | int = None, downcast: dict | None = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Synonym for :meth:`DataFrame.fillna` with ``method='bfill'``. @@ -7184,13 +7194,13 @@ def bfill( @doc(klass=_shared_doc_kwargs["klass"]) def backfill( - self: NDFrameT, + self, *, axis: None | Axis = None, inplace: bool_t = False, limit: None | int = None, downcast: dict | None = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Synonym for :meth:`DataFrame.fillna` with ``method='bfill'``. @@ -7213,7 +7223,7 @@ def backfill( @overload def replace( - self: NDFrameT, + self, to_replace=..., value=..., *, @@ -7221,7 +7231,7 @@ def replace( limit: int | None = ..., regex: bool_t = ..., method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -7239,7 +7249,7 @@ def replace( @overload def replace( - self: NDFrameT, + self, to_replace=..., value=..., *, @@ -7247,7 +7257,7 @@ def replace( limit: int | None = ..., regex: bool_t = ..., method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc( @@ -7257,7 +7267,7 @@ def replace( replace_iloc=_shared_doc_kwargs["replace_iloc"], ) def replace( - self: NDFrameT, + self, to_replace=None, value=lib.no_default, *, @@ -7265,7 +7275,7 @@ def replace( limit: int | None = None, regex: bool_t = False, method: Literal["pad", "ffill", "bfill"] | lib.NoDefault = lib.no_default, - ) -> NDFrameT | None: + ) -> Self | None: if not ( is_scalar(to_replace) or is_re_compilable(to_replace) @@ -7452,7 +7462,7 @@ def replace( return result.__finalize__(self, method="replace") def interpolate( - self: NDFrameT, + self, method: str = "linear", *, axis: Axis = 0, @@ -7462,7 +7472,7 @@ def interpolate( limit_area: str | None = None, downcast: str | None = None, **kwargs, - ) -> NDFrameT | None: + ) -> Self | None: """ Fill NaN values using an interpolation method. @@ -7927,7 +7937,7 @@ def asof(self, where, subset=None): # Action Methods @doc(klass=_shared_doc_kwargs["klass"]) - def isna(self: NDFrameT) -> NDFrameT: + def isna(self) -> Self: """ Detect missing values. @@ -7990,11 +8000,11 @@ def isna(self: NDFrameT) -> NDFrameT: return isna(self).__finalize__(self, method="isna") @doc(isna, klass=_shared_doc_kwargs["klass"]) - def isnull(self: NDFrameT) -> NDFrameT: + def isnull(self) -> Self: return isna(self).__finalize__(self, method="isnull") @doc(klass=_shared_doc_kwargs["klass"]) - def notna(self: NDFrameT) -> NDFrameT: + def notna(self) -> Self: """ Detect existing (non-missing) values. @@ -8057,7 +8067,7 @@ def notna(self: NDFrameT) -> NDFrameT: return notna(self).__finalize__(self, method="notna") @doc(notna, klass=_shared_doc_kwargs["klass"]) - def notnull(self: NDFrameT) -> NDFrameT: + def notnull(self) -> Self: return notna(self).__finalize__(self, method="notnull") @final @@ -8118,14 +8128,14 @@ def _clip_with_one_bound(self, threshold, method, axis, inplace): return self.where(subset, threshold, axis=axis, inplace=inplace) def clip( - self: NDFrameT, + self, lower=None, upper=None, *, axis: Axis | None = None, inplace: bool_t = False, **kwargs, - ) -> NDFrameT | None: + ) -> Self | None: """ Trim values at input threshold(s). @@ -8279,13 +8289,13 @@ def clip( @doc(**_shared_doc_kwargs) def asfreq( - self: NDFrameT, + self, freq: Frequency, method: FillnaOptions | None = None, how: str | None = None, normalize: bool_t = False, fill_value: Hashable = None, - ) -> NDFrameT: + ) -> Self: """ Convert time series to specified frequency. @@ -8403,9 +8413,7 @@ def asfreq( ) @final - def at_time( - self: NDFrameT, time, asof: bool_t = False, axis: Axis | None = None - ) -> NDFrameT: + def at_time(self, time, asof: bool_t = False, axis: Axis | None = None) -> Self: """ Select values at particular time of day (e.g., 9:30AM). @@ -8463,12 +8471,12 @@ def at_time( @final def between_time( - self: NDFrameT, + self, start_time, end_time, inclusive: IntervalClosedType = "both", axis: Axis | None = None, - ) -> NDFrameT: + ) -> Self: """ Select values between particular times of the day (e.g., 9:00-9:30 AM). @@ -8947,7 +8955,7 @@ def resample( ) @final - def first(self: NDFrameT, offset) -> NDFrameT: + def first(self, offset) -> Self: """ Select initial periods of time series data based on a date offset. @@ -9020,7 +9028,7 @@ def first(self: NDFrameT, offset) -> NDFrameT: return self.loc[:end] @final - def last(self: NDFrameT, offset) -> NDFrameT: + def last(self, offset) -> Self: """ Select final periods of time series data based on a date offset. @@ -9085,14 +9093,14 @@ def last(self: NDFrameT, offset) -> NDFrameT: @final def rank( - self: NDFrameT, + self, axis: Axis = 0, method: str = "average", numeric_only: bool_t = False, na_option: str = "keep", ascending: bool_t = True, pct: bool_t = False, - ) -> NDFrameT: + ) -> Self: """ Compute numerical data ranks (1 through n) along axis. @@ -9315,8 +9323,8 @@ def compare( @doc(**_shared_doc_kwargs) def align( - self: NDFrameT, - other: NDFrameTb, + self, + other: NDFrameT, join: AlignJoin = "outer", axis: Axis | None = None, level: Level = None, @@ -9326,7 +9334,7 @@ def align( limit: int | None = None, fill_axis: Axis = 0, broadcast_axis: Axis | None = None, - ) -> tuple[NDFrameT, NDFrameTb]: + ) -> tuple[Self, NDFrameT]: """ Align two objects on their axes with the specified join method. @@ -9800,14 +9808,14 @@ def _where( @overload def where( - self: NDFrameT, + self, cond, other=..., *, inplace: Literal[False] = ..., axis: Axis | None = ..., level: Level = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -9824,14 +9832,14 @@ def where( @overload def where( - self: NDFrameT, + self, cond, other=..., *, inplace: bool_t = ..., axis: Axis | None = ..., level: Level = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc( @@ -9842,14 +9850,14 @@ def where( name_other="mask", ) def where( - self: NDFrameT, + self, cond, other=np.nan, *, inplace: bool_t = False, axis: Axis | None = None, level: Level = None, - ) -> NDFrameT | None: + ) -> Self | None: """ Replace values where the condition is {cond_rev}. @@ -9992,14 +10000,14 @@ def where( @overload def mask( - self: NDFrameT, + self, cond, other=..., *, inplace: Literal[False] = ..., axis: Axis | None = ..., level: Level = ..., - ) -> NDFrameT: + ) -> Self: ... @overload @@ -10016,14 +10024,14 @@ def mask( @overload def mask( - self: NDFrameT, + self, cond, other=..., *, inplace: bool_t = ..., axis: Axis | None = ..., level: Level = ..., - ) -> NDFrameT | None: + ) -> Self | None: ... @doc( @@ -10035,14 +10043,14 @@ def mask( name_other="where", ) def mask( - self: NDFrameT, + self, cond, other=lib.no_default, *, inplace: bool_t = False, axis: Axis | None = None, level: Level = None, - ) -> NDFrameT | None: + ) -> Self | None: inplace = validate_bool_kwarg(inplace, "inplace") cond = common.apply_if_callable(cond, self) @@ -10060,12 +10068,12 @@ def mask( @doc(klass=_shared_doc_kwargs["klass"]) def shift( - self: NDFrameT, + self, periods: int = 1, freq=None, axis: Axis = 0, fill_value: Hashable = None, - ) -> NDFrameT: + ) -> Self: """ Shift index by desired number of periods with an optional time `freq`. @@ -10207,12 +10215,12 @@ def shift( return result.__finalize__(self, method="shift") def truncate( - self: NDFrameT, + self, before=None, after=None, axis: Axis | None = None, copy: bool_t | None = None, - ) -> NDFrameT: + ) -> Self: """ Truncate a Series or DataFrame before and after some index value. @@ -10369,8 +10377,8 @@ def truncate( @final @doc(klass=_shared_doc_kwargs["klass"]) def tz_convert( - self: NDFrameT, tz, axis: Axis = 0, level=None, copy: bool_t | None = None - ) -> NDFrameT: + self, tz, axis: Axis = 0, level=None, copy: bool_t | None = None + ) -> Self: """ Convert tz-aware axis to target time zone. @@ -10450,14 +10458,14 @@ def _tz_convert(ax, tz): @final @doc(klass=_shared_doc_kwargs["klass"]) def tz_localize( - self: NDFrameT, + self, tz, axis: Axis = 0, level=None, copy: bool_t | None = None, ambiguous: TimeAmbiguous = "raise", nonexistent: TimeNonexistent = "raise", - ) -> NDFrameT: + ) -> Self: """ Localize tz-naive index of a Series or DataFrame to target time zone. @@ -10634,11 +10642,11 @@ def _tz_localize(ax, tz, ambiguous, nonexistent): @final def describe( - self: NDFrameT, + self, percentiles=None, include=None, exclude=None, - ) -> NDFrameT: + ) -> Self: """ Generate descriptive statistics. @@ -10883,13 +10891,13 @@ def describe( @final def pct_change( - self: NDFrameT, + self, periods: int = 1, fill_method: Literal["backfill", "bfill", "pad", "ffill"] | None = "pad", limit=None, freq=None, **kwargs, - ) -> NDFrameT: + ) -> Self: """ Percentage change between the current and a prior element. @@ -11013,7 +11021,7 @@ def pct_change( data = _data shifted = data.shift(periods=periods, freq=freq, axis=axis, **kwargs) - # Unsupported left operand type for / ("NDFrameT") + # Unsupported left operand type for / ("Self") rs = data / shifted - 1 # type: ignore[operator] if freq is not None: # Shift method is implemented differently when freq is not None @@ -12026,47 +12034,47 @@ def _inplace_method(self, other, op): ) return self - def __iadd__(self: NDFrameT, other) -> NDFrameT: + def __iadd__(self, other) -> Self: # error: Unsupported left operand type for + ("Type[NDFrame]") return self._inplace_method(other, type(self).__add__) # type: ignore[operator] - def __isub__(self: NDFrameT, other) -> NDFrameT: + def __isub__(self, other) -> Self: # error: Unsupported left operand type for - ("Type[NDFrame]") return self._inplace_method(other, type(self).__sub__) # type: ignore[operator] - def __imul__(self: NDFrameT, other) -> NDFrameT: + def __imul__(self, other) -> Self: # error: Unsupported left operand type for * ("Type[NDFrame]") return self._inplace_method(other, type(self).__mul__) # type: ignore[operator] - def __itruediv__(self: NDFrameT, other) -> NDFrameT: + def __itruediv__(self, other) -> Self: # error: Unsupported left operand type for / ("Type[NDFrame]") return self._inplace_method( other, type(self).__truediv__ # type: ignore[operator] ) - def __ifloordiv__(self: NDFrameT, other) -> NDFrameT: + def __ifloordiv__(self, other) -> Self: # error: Unsupported left operand type for // ("Type[NDFrame]") return self._inplace_method( other, type(self).__floordiv__ # type: ignore[operator] ) - def __imod__(self: NDFrameT, other) -> NDFrameT: + def __imod__(self, other) -> Self: # error: Unsupported left operand type for % ("Type[NDFrame]") return self._inplace_method(other, type(self).__mod__) # type: ignore[operator] - def __ipow__(self: NDFrameT, other) -> NDFrameT: + def __ipow__(self, other) -> Self: # error: Unsupported left operand type for ** ("Type[NDFrame]") return self._inplace_method(other, type(self).__pow__) # type: ignore[operator] - def __iand__(self: NDFrameT, other) -> NDFrameT: + def __iand__(self, other) -> Self: # error: Unsupported left operand type for & ("Type[NDFrame]") return self._inplace_method(other, type(self).__and__) # type: ignore[operator] - def __ior__(self: NDFrameT, other) -> NDFrameT: + def __ior__(self, other) -> Self: # error: Unsupported left operand type for | ("Type[NDFrame]") return self._inplace_method(other, type(self).__or__) # type: ignore[operator] - def __ixor__(self: NDFrameT, other) -> NDFrameT: + def __ixor__(self, other) -> Self: # error: Unsupported left operand type for ^ ("Type[NDFrame]") return self._inplace_method(other, type(self).__xor__) # type: ignore[operator] diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 620823b9703ab..683996586a345 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -14,7 +14,6 @@ Literal, NoReturn, Sequence, - TypeVar, cast, final, overload, @@ -55,6 +54,7 @@ IndexLabel, JoinHow, Level, + Self, Shape, npt, ) @@ -293,9 +293,6 @@ def _new_Index(cls, d): return cls.__new__(cls, **d) -_IndexT = TypeVar("_IndexT", bound="Index") - - class Index(IndexOpsMixin, PandasObject): """ Immutable sequence used for indexing and alignment. @@ -357,7 +354,7 @@ class Index(IndexOpsMixin, PandasObject): # given the dtypes of the passed arguments @final - def _left_indexer_unique(self: _IndexT, other: _IndexT) -> npt.NDArray[np.intp]: + def _left_indexer_unique(self, other: Index) -> npt.NDArray[np.intp]: # Caller is responsible for ensuring other.dtype == self.dtype sv = self._get_join_target() ov = other._get_join_target() @@ -369,7 +366,7 @@ def _left_indexer_unique(self: _IndexT, other: _IndexT) -> npt.NDArray[np.intp]: @final def _left_indexer( - self: _IndexT, other: _IndexT + self, other: Index ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: # Caller is responsible for ensuring other.dtype == self.dtype sv = self._get_join_target() @@ -383,7 +380,7 @@ def _left_indexer( @final def _inner_indexer( - self: _IndexT, other: _IndexT + self, other: Index ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: # Caller is responsible for ensuring other.dtype == self.dtype sv = self._get_join_target() @@ -397,7 +394,7 @@ def _inner_indexer( @final def _outer_indexer( - self: _IndexT, other: _IndexT + self, other: Index ) -> tuple[ArrayLike, npt.NDArray[np.intp], npt.NDArray[np.intp]]: # Caller is responsible for ensuring other.dtype == self.dtype sv = self._get_join_target() @@ -628,9 +625,7 @@ def _dtype_to_subclass(cls, dtype: DtypeObj): # See each method's docstring. @classmethod - def _simple_new( - cls: type[_IndexT], values: ArrayLike, name: Hashable = None - ) -> _IndexT: + def _simple_new(cls, values: ArrayLike, name: Hashable = None) -> Self: """ We require that we have a dtype compat for the values. If we are passed a non-dtype compat, then coerce using the constructor. @@ -666,7 +661,7 @@ def _with_infer(cls, *args, **kwargs): return result @cache_readonly - def _constructor(self: _IndexT) -> type[_IndexT]: + def _constructor(self) -> type[Self]: return type(self) @final @@ -725,7 +720,7 @@ def _format_duplicate_message(self) -> DataFrame: # -------------------------------------------------------------------- # Index Internals Methods - def _shallow_copy(self: _IndexT, values, name: Hashable = no_default) -> _IndexT: + def _shallow_copy(self, values, name: Hashable = no_default) -> Self: """ Create a new Index with the same class as the caller, don't copy the data, use the same object attributes with passed in attributes taking @@ -742,7 +737,7 @@ def _shallow_copy(self: _IndexT, values, name: Hashable = no_default) -> _IndexT return self._simple_new(values, name=name) - def _view(self: _IndexT) -> _IndexT: + def _view(self) -> Self: """ fastpath to make a shallow copy, i.e. new object with same data. """ @@ -752,7 +747,7 @@ def _view(self: _IndexT) -> _IndexT: return result @final - def _rename(self: _IndexT, name: Hashable) -> _IndexT: + def _rename(self, name: Hashable) -> Self: """ fastpath for rename if new name is already validated. """ @@ -1150,10 +1145,10 @@ def repeat(self, repeats, axis=None): # Copying Methods def copy( - self: _IndexT, + self, name: Hashable | None = None, deep: bool = False, - ) -> _IndexT: + ) -> Self: """ Make a copy of this object. @@ -1185,11 +1180,11 @@ def copy( return new_index @final - def __copy__(self: _IndexT, **kwargs) -> _IndexT: + def __copy__(self, **kwargs) -> Self: return self.copy(**kwargs) @final - def __deepcopy__(self: _IndexT, memo=None) -> _IndexT: + def __deepcopy__(self, memo=None) -> Self: """ Parameters ---------- @@ -1410,7 +1405,7 @@ def _summary(self, name=None) -> str_t: # -------------------------------------------------------------------- # Conversion Methods - def to_flat_index(self: _IndexT) -> _IndexT: + def to_flat_index(self) -> Self: """ Identity method. @@ -1675,9 +1670,7 @@ def _set_names(self, values, *, level=None) -> None: names = property(fset=_set_names, fget=_get_names) @overload - def set_names( - self: _IndexT, names, *, level=..., inplace: Literal[False] = ... - ) -> _IndexT: + def set_names(self, names, *, level=..., inplace: Literal[False] = ...) -> Self: ... @overload @@ -1685,14 +1678,10 @@ def set_names(self, names, *, level=..., inplace: Literal[True]) -> None: ... @overload - def set_names( - self: _IndexT, names, *, level=..., inplace: bool = ... - ) -> _IndexT | None: + def set_names(self, names, *, level=..., inplace: bool = ...) -> Self | None: ... - def set_names( - self: _IndexT, names, *, level=None, inplace: bool = False - ) -> _IndexT | None: + def set_names(self, names, *, level=None, inplace: bool = False) -> Self | None: """ Set Index or MultiIndex name. @@ -1858,7 +1847,7 @@ def nlevels(self) -> int: """ return 1 - def _sort_levels_monotonic(self: _IndexT) -> _IndexT: + def _sort_levels_monotonic(self) -> Self: """ Compat with MultiIndex. """ @@ -2845,7 +2834,7 @@ def fillna(self, value=None, downcast=None): ) return self._view() - def dropna(self: _IndexT, how: AnyAll = "any") -> _IndexT: + def dropna(self, how: AnyAll = "any") -> Self: """ Return Index without NA/NaN values. @@ -2870,7 +2859,7 @@ def dropna(self: _IndexT, how: AnyAll = "any") -> _IndexT: # -------------------------------------------------------------------- # Uniqueness Methods - def unique(self: _IndexT, level: Hashable | None = None) -> _IndexT: + def unique(self, level: Hashable | None = None) -> Self: """ Return unique values in the index. @@ -2900,7 +2889,7 @@ def unique(self: _IndexT, level: Hashable | None = None) -> _IndexT: result = super().unique() return self._shallow_copy(result) - def drop_duplicates(self: _IndexT, *, keep: DropKeep = "first") -> _IndexT: + def drop_duplicates(self, *, keep: DropKeep = "first") -> Self: """ Return Index with duplicate values removed. @@ -4850,18 +4839,18 @@ def _join_monotonic( return join_index, lidx, ridx def _wrap_joined_index( - self: _IndexT, + self, joined: ArrayLike, - other: _IndexT, + other: Self, lidx: npt.NDArray[np.intp], ridx: npt.NDArray[np.intp], - ) -> _IndexT: + ) -> Self: assert other.dtype == self.dtype if isinstance(self, ABCMultiIndex): name = self.names if self.names == other.names else None # error: Incompatible return value type (got "MultiIndex", - # expected "_IndexT") + # expected "Self") mask = lidx == -1 join_idx = self.take(lidx) right = other.take(ridx) @@ -5185,7 +5174,7 @@ def __getitem__(self, key): # didn't override __getitem__ return self._constructor._simple_new(result, name=self._name) - def _getitem_slice(self: _IndexT, slobj: slice) -> _IndexT: + def _getitem_slice(self, slobj: slice) -> Self: """ Fastpath for __getitem__ when we know we have a slice. """ @@ -6557,7 +6546,7 @@ def slice_locs(self, start=None, end=None, step=None) -> tuple[int, int]: return start_slice, end_slice - def delete(self: _IndexT, loc) -> _IndexT: + def delete(self, loc) -> Self: """ Make new Index with passed location(-s) deleted. diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 17fac22f578db..1133ea6be26ac 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -12,7 +12,6 @@ Any, Callable, Sequence, - TypeVar, cast, final, ) @@ -69,6 +68,7 @@ from pandas._typing import ( Axis, + Self, npt, ) @@ -76,9 +76,6 @@ _index_doc_kwargs = dict(ibase._index_doc_kwargs) -_T = TypeVar("_T", bound="DatetimeIndexOpsMixin") -_TDT = TypeVar("_TDT", bound="DatetimeTimedeltaMixin") - class DatetimeIndexOpsMixin(NDArrayBackedExtensionIndex, ABC): """ @@ -358,7 +355,7 @@ def _maybe_cast_slice_bound(self, label, side: str): # -------------------------------------------------------------------- # Arithmetic Methods - def shift(self: _T, periods: int = 1, freq=None) -> _T: + def shift(self, periods: int = 1, freq=None) -> Self: """ Shift index by desired number of time frequency increments. @@ -424,7 +421,7 @@ class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, ABC): def unit(self) -> str: return self._data.unit - def as_unit(self: _TDT, unit: str) -> _TDT: + def as_unit(self, unit: str) -> Self: """ Convert to a dtype with the given unit resolution. @@ -449,7 +446,7 @@ def values(self) -> np.ndarray: return self._data._ndarray @doc(DatetimeIndexOpsMixin.shift) - def shift(self: _TDT, periods: int = 1, freq=None) -> _TDT: + def shift(self, periods: int = 1, freq=None) -> Self: if freq is not None and freq != self.freq: if isinstance(freq, str): freq = to_offset(freq) @@ -569,7 +566,7 @@ def _fast_intersect(self, other, sort): return result - def _can_fast_intersect(self: _T, other: _T) -> bool: + def _can_fast_intersect(self, other: Self) -> bool: # Note: we only get here with len(self) > 0 and len(other) > 0 if self.freq is None: return False @@ -587,7 +584,7 @@ def _can_fast_intersect(self: _T, other: _T) -> bool: # GH#42104 return self.freq.n == 1 - def _can_fast_union(self: _T, other: _T) -> bool: + def _can_fast_union(self, other: Self) -> bool: # Assumes that type(self) == type(other), as per the annotation # The ability to fast_union also implies that `freq` should be # retained on union. @@ -617,7 +614,7 @@ def _can_fast_union(self: _T, other: _T) -> bool: # Only need to "adjoin", not overlap return (right_start == left_end + freq) or right_start in left - def _fast_union(self: _TDT, other: _TDT, sort=None) -> _TDT: + def _fast_union(self, other: Self, sort=None) -> Self: # Caller is responsible for ensuring self and other are non-empty # to make our life easier, "sort" the two ranges diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 8c8de96ad5b54..dd56b984227de 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -29,7 +29,6 @@ from pandas.core.arrays import IntervalArray from pandas.core.arrays._mixins import NDArrayBackedExtensionArray -_T = TypeVar("_T", bound="NDArrayBackedExtensionIndex") _ExtensionIndexT = TypeVar("_ExtensionIndexT", bound="ExtensionIndex") diff --git a/pandas/core/series.py b/pandas/core/series.py index e8d6491e43007..2226910bde448 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -173,6 +173,7 @@ QuantileInterpolation, Renamer, Scalar, + Self, SingleManager, SortKind, StorageOptions, @@ -4593,7 +4594,7 @@ def align( limit: int | None = None, fill_axis: Axis = 0, broadcast_axis: Axis | None = None, - ) -> tuple[Series, NDFrameT]: + ) -> tuple[Self, NDFrameT]: return super().align( other, join=join,