From a040752a0d46a288605c48d7f90ec6d805d9c88b Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 19 Oct 2020 17:29:11 -0700 Subject: [PATCH 1/8] TYP: use @final in Index --- pandas/core/indexes/base.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index d1a52011a3ad7..947f2a1ad932b 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -17,6 +17,7 @@ TypeVar, Union, cast, + final, ) import warnings @@ -1449,6 +1450,7 @@ def _sort_levels_monotonic(self): """ return self + @final def _validate_index_level(self, level): """ Validate index level. @@ -2072,6 +2074,7 @@ def is_mixed(self) -> bool: ) return self.inferred_type in ["mixed"] + @final def holds_integer(self) -> bool: """ Whether the type is an integer type. @@ -2333,6 +2336,7 @@ def unique(self, level=None): result = super().unique() return self._shallow_copy(result) + @final def drop_duplicates(self, keep="first"): """ Return Index with duplicate values removed. @@ -2503,6 +2507,7 @@ def __nonzero__(self): # -------------------------------------------------------------------- # Set Operation Methods + @final def _get_reconciled_name_object(self, other): """ If the result of a set operation will be self, @@ -2514,6 +2519,7 @@ def _get_reconciled_name_object(self, other): return self.rename(name) return self + @final def _union_incompatible_dtypes(self, other, sort): """ Casts this and other index to object dtype to allow the formation @@ -2554,6 +2560,7 @@ def _can_union_without_object_cast(self, other) -> bool: """ return type(self) is type(other) and is_dtype_equal(self.dtype, other.dtype) + @final def _validate_sort_keyword(self, sort): if sort not in [None, False]: raise ValueError( @@ -2690,6 +2697,7 @@ def _union(self, other, sort): return self._shallow_copy(result) + @final def _wrap_setop_result(self, other, result): if isinstance(self, (ABCDatetimeIndex, ABCTimedeltaIndex)) and isinstance( result, np.ndarray @@ -3100,6 +3108,7 @@ def _convert_tolerance(self, tolerance, target): raise ValueError("list-like tolerance size must match target index size") return tolerance + @final def _get_fill_indexer( self, target: "Index", method: str_t, limit=None, tolerance=None ) -> np.ndarray: @@ -3119,6 +3128,7 @@ def _get_fill_indexer( indexer = self._filter_indexer_tolerance(target_values, indexer, tolerance) return indexer + @final def _get_fill_indexer_searchsorted( self, target: "Index", method: str_t, limit=None ) -> np.ndarray: @@ -3152,6 +3162,7 @@ def _get_fill_indexer_searchsorted( indexer[indexer == len(self)] = -1 return indexer + @final def _get_nearest_indexer(self, target: "Index", limit, tolerance) -> np.ndarray: """ Get the indexer for the nearest index labels; requires an index with @@ -3175,6 +3186,7 @@ def _get_nearest_indexer(self, target: "Index", limit, tolerance) -> np.ndarray: indexer = self._filter_indexer_tolerance(target_values, indexer, tolerance) return indexer + @final def _filter_indexer_tolerance( self, target: Union["Index", np.ndarray, ExtensionArray], @@ -3198,6 +3210,7 @@ def _get_partial_string_timestamp_match_key(self, key): # GH#10331 return key + @final def _validate_positional_slice(self, key: slice): """ For positional indexing, a slice must have either int or None @@ -3331,6 +3344,7 @@ def _convert_list_indexer(self, keyarr): """ return None + @final def _invalid_indexer(self, form: str_t, key): """ Consistent invalid indexer message. @@ -3343,6 +3357,7 @@ def _invalid_indexer(self, form: str_t, key): # -------------------------------------------------------------------- # Reindex Methods + @final def _can_reindex(self, indexer): """ Check if we are allowing reindexing with this particular indexer. @@ -3608,6 +3623,7 @@ def join(self, other, how="left", level=None, return_indexers=False, sort=False) else: return join_index + @final def _join_multi(self, other, how, return_indexers=True): from pandas.core.indexes.multi import MultiIndex from pandas.core.reshape.merge import restore_dropped_levels_multijoin @@ -3687,6 +3703,7 @@ def _join_multi(self, other, how, return_indexers=True): return result[0], result[2], result[1] return result + @final def _join_non_unique(self, other, how="left", return_indexers=False): from pandas.core.reshape.merge import get_join_indexers @@ -4057,6 +4074,7 @@ def _string_data_error(cls, data): "to explicitly cast to a numeric type" ) + @final def _coerce_scalar_to_index(self, item): """ We need to coerce a scalar to a compat for our index type. @@ -4087,6 +4105,7 @@ def _validate_fill_value(self, value): """ return value + @final def _validate_scalar(self, value): """ Check that this is a scalar value that we can use for setitem-like @@ -4157,9 +4176,11 @@ def __contains__(self, key: Any) -> bool: except (OverflowError, TypeError, ValueError): return False + @final def __hash__(self): raise TypeError(f"unhashable type: {repr(type(self).__name__)}") + @final def __setitem__(self, key, value): raise TypeError("Index does not support mutable operations") @@ -4200,6 +4221,7 @@ def __getitem__(self, key): else: return result + @final def _can_hold_identifiers_and_holds_name(self, name) -> bool: """ Faster check for ``name in self`` when we know `name` is a Python @@ -4353,6 +4375,7 @@ def equals(self, other: object) -> bool: return array_equivalent(self._values, other._values) + @final def identical(self, other) -> bool: """ Similar to equals, but checks that object attributes and types are also equal. @@ -4666,6 +4689,7 @@ def argsort(self, *args, **kwargs) -> np.ndarray: return result.argsort(*args, **kwargs) + @final def get_value(self, series: "Series", key): """ Fast lookup of value from 1-dimensional ndarray. @@ -4732,6 +4756,7 @@ def _get_values_for_loc(self, series: "Series", loc, key): return series.iloc[loc] + @final def set_value(self, arr, key, value): """ Fast lookup of value from 1-dimensional ndarray. @@ -4795,6 +4820,7 @@ def get_indexer_non_unique(self, target): indexer, missing = self._engine.get_indexer_non_unique(tgt_values) return ensure_platform_int(indexer), missing + @final def get_indexer_for(self, target, **kwargs): """ Guaranteed return of an indexer even when non-unique. @@ -4822,6 +4848,7 @@ def _index_as_unique(self): """ return self.is_unique + @final def _maybe_promote(self, other: "Index"): """ When dealing with an object-dtype Index and a non-object Index, see @@ -4921,6 +4948,7 @@ def map(self, mapper, na_action=None): return Index(new_values, **attributes) # TODO: De-duplicate with map, xref GH#32349 + @final def _transform_index(self, func, level=None) -> "Index": """ Apply function to all values found in index. @@ -5096,6 +5124,7 @@ def _maybe_cast_indexer(self, key): return com.cast_scalar_indexer(key) return key + @final def _validate_indexer(self, form: str_t, key, kind: str_t): """ If we are positional indexer, validate that we have appropriate From f7b96142847c07620ca9406bd22e022b1cdb197b Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 19 Oct 2020 17:43:45 -0700 Subject: [PATCH 2/8] TYP: use @final --- pandas/core/indexes/base.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 947f2a1ad932b..23d52bbfea426 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -446,6 +446,7 @@ def _simple_new(cls, values, name: Label = None): def _constructor(self): return type(self) + @final def _maybe_check_unique(self): """ Check that an Index has no duplicates. @@ -466,6 +467,7 @@ def _maybe_check_unique(self): raise DuplicateLabelError(msg) + @final def _format_duplicate_message(self): """ Construct the DataFrame for a DuplicateLabelError. @@ -495,6 +497,7 @@ def _format_duplicate_message(self): # -------------------------------------------------------------------- # Index Internals Methods + @final def _get_attributes_dict(self): """ Return an attributes dict for my class. @@ -523,6 +526,7 @@ def _shallow_copy(self, values=None, name: Label = no_default): result._cache = self._cache return result + @final def is_(self, other) -> bool: """ More flexible, faster check like ``is`` but that works through views. @@ -553,12 +557,14 @@ def is_(self, other) -> bool: else: return self._id is other._id + @final def _reset_identity(self) -> None: """ Initializes or resets ``_id`` attribute with new object. """ self._id = _Identity(object()) + @final def _cleanup(self): self._engine.clear_mapping() @@ -618,6 +624,7 @@ def dtype(self): """ return self._data.dtype + @final def ravel(self, order="C"): """ Return an ndarray of the flattened values of the underlying data. @@ -872,9 +879,11 @@ def copy( new_index = new_index.astype(dtype) return new_index + @final def __copy__(self, **kwargs): return self.copy(**kwargs) + @final def __deepcopy__(self, memo=None): """ Parameters @@ -1243,6 +1252,7 @@ def name(self, value): maybe_extract_name(value, None, type(self)) self._name = value + @final def _validate_names(self, name=None, names=None, deep: bool = False) -> List[Label]: """ Handles the quirks of having a singular 'name' parameter for general @@ -1305,6 +1315,7 @@ def _set_names(self, values, level=None): names = property(fset=_set_names, fget=_get_names) + @final def set_names(self, names, level=None, inplace: bool = False): """ Set Index or MultiIndex name. @@ -1552,6 +1563,7 @@ def _get_level_values(self, level): get_level_values = _get_level_values + @final def droplevel(self, level=0): """ Return index with requested level(s) removed. @@ -2152,6 +2164,7 @@ def hasnans(self) -> bool: else: return False + @final def isna(self): """ Detect missing values. @@ -2209,6 +2222,7 @@ def isna(self): isnull = isna + @final def notna(self): """ Detect existing (non-missing) values. @@ -2487,15 +2501,19 @@ def __iadd__(self, other): # alias for __add__ return self + other + @final def __and__(self, other): return self.intersection(other) + @final def __or__(self, other): return self.union(other) + @final def __xor__(self, other): return self.symmetric_difference(other) + @final def __nonzero__(self): raise ValueError( f"The truth value of a {type(self).__name__} is ambiguous. " @@ -4395,6 +4413,7 @@ def identical(self, other) -> bool: and type(self) == type(other) ) + @final def asof(self, label): """ Return the label from the index, or, if not present, the previous one. @@ -4583,6 +4602,7 @@ def sort_values( else: return sorted_index + @final def sort(self, *args, **kwargs): """ Use sort_values instead. From 031b2f8b8caed49fc3dea36cb2d74ff76e47ddbf Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 19 Oct 2020 17:50:53 -0700 Subject: [PATCH 3/8] TYP: use @final --- pandas/core/indexes/base.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 23d52bbfea426..f0cba13853518 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4076,6 +4076,7 @@ def where(self, cond, other=None): return Index(values, dtype=dtype, name=self.name) # construction helpers + @final @classmethod def _scalar_data_error(cls, data): # We return the TypeError so that we can raise it from the constructor @@ -4085,6 +4086,7 @@ def _scalar_data_error(cls, data): f"kind, {repr(data)} was passed" ) + @final @classmethod def _string_data_error(cls, data): raise TypeError( @@ -4517,6 +4519,7 @@ def asof_locs(self, where, mask): return result + @final def sort_values( self, return_indexer: bool = False, @@ -4899,6 +4902,7 @@ def _is_comparable_dtype(self, dtype: DtypeObj) -> bool: """ return True + @final def groupby(self, values) -> PrettyDict[Hashable, np.ndarray]: """ Group the index labels by a given array of values. @@ -5612,6 +5616,7 @@ def all(self): self._maybe_disable_logical_methods("all") return np.all(self.values) + @final def _maybe_disable_logical_methods(self, opname: str_t): """ raise if this Index subclass does not support any or all. From c9279613491bd5e471740d7e2d8d4c1039e146a6 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 20 Oct 2020 08:51:37 -0700 Subject: [PATCH 4/8] TYP: use @final --- pandas/core/indexes/base.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index f0cba13853518..f34cc8a951853 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1663,6 +1663,7 @@ def _get_grouper_for_level(self, mapper, level=None): # -------------------------------------------------------------------- # Introspection Methods + @final @property def is_monotonic(self) -> bool: """ @@ -1777,6 +1778,7 @@ def has_duplicates(self) -> bool: """ return not self.is_unique + @final def is_boolean(self) -> bool: """ Check if the Index only consists of booleans. @@ -1812,6 +1814,7 @@ def is_boolean(self) -> bool: """ return self.inferred_type in ["boolean"] + @final def is_integer(self) -> bool: """ Check if the Index only consists of integers. @@ -1847,6 +1850,7 @@ def is_integer(self) -> bool: """ return self.inferred_type in ["integer"] + @final def is_floating(self) -> bool: """ Check if the Index is a floating type. @@ -1890,6 +1894,7 @@ def is_floating(self) -> bool: """ return self.inferred_type in ["floating", "mixed-integer-float", "integer-na"] + @final def is_numeric(self) -> bool: """ Check if the Index only consists of numeric data. @@ -1933,6 +1938,7 @@ def is_numeric(self) -> bool: """ return self.inferred_type in ["integer", "floating"] + @final def is_object(self) -> bool: """ Check if the Index is of the object dtype. @@ -1973,6 +1979,7 @@ def is_object(self) -> bool: """ return is_object_dtype(self.dtype) + @final def is_categorical(self) -> bool: """ Check if the Index holds categorical data. @@ -2016,6 +2023,7 @@ def is_categorical(self) -> bool: """ return self.inferred_type in ["categorical"] + @final def is_interval(self) -> bool: """ Check if the Index holds Interval objects. @@ -2049,6 +2057,7 @@ def is_interval(self) -> bool: """ return self.inferred_type in ["interval"] + @final def is_mixed(self) -> bool: """ Check if the Index holds data with mixed data types. From da2f1d467180dcc2b4d9f31d0b298145937ee122 Mon Sep 17 00:00:00 2001 From: Brock Date: Tue, 20 Oct 2020 09:02:40 -0700 Subject: [PATCH 5/8] TYP: use @final --- pandas/core/indexes/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index f34cc8a951853..7f87513d17e09 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -2157,6 +2157,7 @@ def _isnan(self): return values @cache_readonly + @final def _nan_idxs(self): if self._can_hold_na: return self._isnan.nonzero()[0] From ee6ba1daa6d1a777a43bf5c61693fd76bc30c745 Mon Sep 17 00:00:00 2001 From: Brock Date: Fri, 30 Oct 2020 15:40:12 -0700 Subject: [PATCH 6/8] import final only im TYPE_CHECKING --- pandas/core/indexes/base.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 7f87513d17e09..1b190b18eac84 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -17,7 +17,6 @@ TypeVar, Union, cast, - final, ) import warnings @@ -101,7 +100,12 @@ ) if TYPE_CHECKING: + from typing import final + from pandas import MultiIndex, RangeIndex, Series +else: + # we cannot unconditionally import final on python<3.8 + final = lambda x: x __all__ = ["Index"] From 9a1bb100e8925e7fd8d99542cf4d209017839ca3 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 31 Oct 2020 09:01:06 -0700 Subject: [PATCH 7/8] put final in pd._typing --- pandas/_typing.py | 6 ++++++ pandas/core/indexes/base.py | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pandas/_typing.py b/pandas/_typing.py index a9177106535fc..2c6b7634cb7f6 100644 --- a/pandas/_typing.py +++ b/pandas/_typing.py @@ -27,6 +27,8 @@ # and use a string literal forward reference to it in subsequent types # https://mypy.readthedocs.io/en/latest/common_issues.html#import-cycles if TYPE_CHECKING: + from typing import final + from pandas._libs import Period, Timedelta, Timestamp from pandas.core.dtypes.dtypes import ExtensionDtype @@ -39,6 +41,10 @@ from pandas.core.series import Series from pandas.io.formats.format import EngFormatter +else: + # typing.final does not exist until py38 + final = lambda x: x + # array-like diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 1b190b18eac84..7f87513d17e09 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -17,6 +17,7 @@ TypeVar, Union, cast, + final, ) import warnings @@ -100,12 +101,7 @@ ) if TYPE_CHECKING: - from typing import final - from pandas import MultiIndex, RangeIndex, Series -else: - # we cannot unconditionally import final on python<3.8 - final = lambda x: x __all__ = ["Index"] From 1c4b0e71db37134b0b5fc2865d983473d41cc724 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 31 Oct 2020 11:06:09 -0700 Subject: [PATCH 8/8] fixup wrong import --- pandas/core/indexes/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 748ca92ac3238..91d27a4406b86 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -17,7 +17,6 @@ TypeVar, Union, cast, - final, ) import warnings @@ -28,7 +27,7 @@ from pandas._libs.lib import is_datetime_array, no_default from pandas._libs.tslibs import IncompatibleFrequency, OutOfBoundsDatetime, Timestamp from pandas._libs.tslibs.timezones import tz_compare -from pandas._typing import AnyArrayLike, Dtype, DtypeObj, Label +from pandas._typing import AnyArrayLike, Dtype, DtypeObj, Label, final from pandas.compat.numpy import function as nv from pandas.errors import DuplicateLabelError, InvalidIndexError from pandas.util._decorators import Appender, cache_readonly, doc