From 47d697709ca391d214ffb5e810c49c206eef54e3 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 8 Oct 2020 15:51:02 -0700 Subject: [PATCH 1/2] TYP: untyped decorators --- pandas/core/arrays/_mixins.py | 2 +- pandas/core/arrays/base.py | 8 ++--- pandas/core/arrays/boolean.py | 5 +-- pandas/core/arrays/categorical.py | 2 +- pandas/core/arrays/datetimes.py | 3 +- pandas/core/arrays/interval.py | 7 ++--- pandas/core/arrays/masked.py | 4 +-- pandas/core/arrays/numpy_.py | 3 +- pandas/core/arrays/sparse/array.py | 3 +- pandas/core/arrays/timedeltas.py | 3 +- pandas/core/base.py | 49 +---------------------------- pandas/core/indexes/base.py | 4 +-- pandas/core/indexes/category.py | 2 +- pandas/core/indexes/datetimelike.py | 10 +++--- pandas/core/indexes/datetimes.py | 2 +- pandas/core/indexes/interval.py | 6 ++-- pandas/core/indexes/multi.py | 4 +-- pandas/core/indexes/numeric.py | 6 ++-- pandas/core/indexes/period.py | 4 +-- pandas/core/indexes/range.py | 6 ++-- pandas/core/indexes/timedeltas.py | 4 +-- pandas/core/series.py | 47 +++++++++++++++++++++++++++ 22 files changed, 94 insertions(+), 90 deletions(-) diff --git a/pandas/core/arrays/_mixins.py b/pandas/core/arrays/_mixins.py index 95a003efbe1d0..a3b4e2ab52c11 100644 --- a/pandas/core/arrays/_mixins.py +++ b/pandas/core/arrays/_mixins.py @@ -98,7 +98,7 @@ def ndim(self) -> int: def size(self) -> int: return np.prod(self.shape) - @cache_readonly + @property def nbytes(self) -> int: return self._ndarray.nbytes diff --git a/pandas/core/arrays/base.py b/pandas/core/arrays/base.py index 94d6428b44043..3ca5224f5be49 100644 --- a/pandas/core/arrays/base.py +++ b/pandas/core/arrays/base.py @@ -16,7 +16,7 @@ from pandas.compat import set_function_name from pandas.compat.numpy import function as nv from pandas.errors import AbstractMethodError -from pandas.util._decorators import Appender, Substitution +from pandas.util._decorators import Appender, Substitution, cache_readonly from pandas.util._validators import validate_fillna_kwargs from pandas.core.dtypes.cast import maybe_cast_to_extension_array @@ -395,7 +395,7 @@ def to_numpy( # Required attributes # ------------------------------------------------------------------------ - @property + @cache_readonly def dtype(self) -> ExtensionDtype: """ An instance of 'ExtensionDtype'. @@ -409,14 +409,14 @@ def shape(self) -> Tuple[int, ...]: """ return (len(self),) - @property + @cache_readonly def size(self) -> int: """ The number of elements in the array. """ return np.prod(self.shape) - @property + @cache_readonly def ndim(self) -> int: """ Extension Arrays are only allowed to be 1-dimensional. diff --git a/pandas/core/arrays/boolean.py b/pandas/core/arrays/boolean.py index 4dd117e407961..842c5ee60568c 100644 --- a/pandas/core/arrays/boolean.py +++ b/pandas/core/arrays/boolean.py @@ -8,6 +8,7 @@ from pandas._typing import ArrayLike from pandas.compat import set_function_name from pandas.compat.numpy import function as nv +from pandas.util._decorators import cache_readonly from pandas.core.dtypes.common import ( is_bool_dtype, @@ -68,7 +69,7 @@ def type(self) -> Type: # type: ignore[override] def kind(self) -> str: return "b" - @property + @cache_readonly def numpy_dtype(self) -> np.dtype: return np.dtype("bool") @@ -269,7 +270,7 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False): self._dtype = BooleanDtype() super().__init__(values, mask, copy=copy) - @property + @cache_readonly def dtype(self) -> BooleanDtype: return self._dtype diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index 1a8861af10ed1..60ac580c429c9 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -373,7 +373,7 @@ def __init__( self._dtype = self._dtype.update_dtype(dtype) self._codes = coerce_indexer_dtype(codes, dtype.categories) - @property + @cache_readonly def dtype(self) -> CategoricalDtype: """ The :class:`~pandas.api.types.CategoricalDtype` for this instance. diff --git a/pandas/core/arrays/datetimes.py b/pandas/core/arrays/datetimes.py index 1e879e32bed5f..f791ef9675028 100644 --- a/pandas/core/arrays/datetimes.py +++ b/pandas/core/arrays/datetimes.py @@ -23,6 +23,7 @@ tzconversion, ) from pandas.errors import PerformanceWarning +from pandas.util._decorators import cache_readonly from pandas.core.dtypes.common import ( DT64NS_DTYPE, @@ -484,7 +485,7 @@ def _maybe_clear_freq(self): def _box_func(self, x) -> Union[Timestamp, NaTType]: return Timestamp(x, freq=self.freq, tz=self.tz) - @property + @cache_readonly def dtype(self) -> Union[np.dtype, DatetimeTZDtype]: """ The dtype for the DatetimeArray. diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index a06e0c74ec03b..baa0118c72b34 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -12,7 +12,7 @@ intervals_to_interval_bounds, ) from pandas.compat.numpy import function as nv -from pandas.util._decorators import Appender +from pandas.util._decorators import Appender, cache_readonly from pandas.core.dtypes.cast import maybe_convert_platform from pandas.core.dtypes.common import ( @@ -144,7 +144,6 @@ ) ) class IntervalArray(IntervalMixin, ExtensionArray): - ndim = 1 can_hold_na = True _na_value = _fill_value = np.nan @@ -522,7 +521,7 @@ def _shallow_copy(self, left, right): # --------------------------------------------------------------------- # Descriptive - @property + @cache_readonly def dtype(self): return IntervalDtype(self.left.dtype) @@ -530,7 +529,7 @@ def dtype(self): def nbytes(self) -> int: return self.left.nbytes + self.right.nbytes - @property + @cache_readonly def size(self) -> int: # Avoid materializing self.values return self.left.size diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py index 97ade0dc70843..fef747cc812fd 100644 --- a/pandas/core/arrays/masked.py +++ b/pandas/core/arrays/masked.py @@ -45,7 +45,7 @@ def numpy_dtype(self) -> np.dtype: """ Return an instance of our numpy dtype """ return np.dtype(self.type) - @cache_readonly + @property def kind(self) -> str: return self.numpy_dtype.kind @@ -95,7 +95,7 @@ def __init__(self, values: np.ndarray, mask: np.ndarray, copy: bool = False): self._data = values self._mask = mask - @property + @cache_readonly def dtype(self) -> BaseMaskedDtype: raise AbstractMethodError(self) diff --git a/pandas/core/arrays/numpy_.py b/pandas/core/arrays/numpy_.py index b5103fb7f9d5d..a2a4e30a7f157 100644 --- a/pandas/core/arrays/numpy_.py +++ b/pandas/core/arrays/numpy_.py @@ -7,6 +7,7 @@ from pandas._libs import lib from pandas._typing import Scalar from pandas.compat.numpy import function as nv +from pandas.util._decorators import cache_readonly from pandas.core.dtypes.dtypes import ExtensionDtype from pandas.core.dtypes.missing import isna @@ -194,7 +195,7 @@ def _from_backing_data(self, arr: np.ndarray) -> "PandasArray": # ------------------------------------------------------------------------ # Data - @property + @cache_readonly def dtype(self) -> PandasDtype: return self._dtype diff --git a/pandas/core/arrays/sparse/array.py b/pandas/core/arrays/sparse/array.py index d4ec641794fc2..748c36dc85870 100644 --- a/pandas/core/arrays/sparse/array.py +++ b/pandas/core/arrays/sparse/array.py @@ -17,6 +17,7 @@ import pandas.compat as compat from pandas.compat.numpy import function as nv from pandas.errors import PerformanceWarning +from pandas.util._decorators import cache_readonly from pandas.core.dtypes.cast import ( astype_nansafe, @@ -515,7 +516,7 @@ def sp_values(self) -> np.ndarray: """ return self._sparse_values - @property + @cache_readonly def dtype(self): return self._dtype diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 0853664c766bb..5bc9fd1af92ef 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -19,6 +19,7 @@ from pandas._libs.tslibs.fields import get_timedelta_field from pandas._libs.tslibs.timedeltas import array_to_timedelta64, parse_timedelta_unit from pandas.compat.numpy import function as nv +from pandas.util._decorators import cache_readonly from pandas.core.dtypes.common import ( DT64NS_DTYPE, @@ -122,7 +123,7 @@ class TimedeltaArray(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps): def _box_func(self, x) -> Union[Timedelta, NaTType]: return Timedelta(x, unit="ns") - @property + @cache_readonly def dtype(self) -> np.dtype: """ The dtype for the TimedeltaArray. diff --git a/pandas/core/base.py b/pandas/core/base.py index 1063e742e38c8..3a8ba33f34dec 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -198,7 +198,7 @@ def _selected_obj(self): else: return self.obj[self._selection] - @cache_readonly + @property def ndim(self) -> int: return self._selected_obj.ndim @@ -1315,53 +1315,6 @@ def nunique(self, dropna: bool = True) -> int: n -= 1 return n - @property - def is_unique(self) -> bool: - """ - Return boolean if values in the object are unique. - - Returns - ------- - bool - """ - return self.nunique(dropna=False) == len(self) - - @property - def is_monotonic(self) -> bool: - """ - Return boolean if values in the object are - monotonic_increasing. - - Returns - ------- - bool - """ - from pandas import Index - - return Index(self).is_monotonic - - @property - def is_monotonic_increasing(self) -> bool: - """ - Alias for is_monotonic. - """ - # mypy complains if we alias directly - return self.is_monotonic - - @property - def is_monotonic_decreasing(self) -> bool: - """ - Return boolean if values in the object are - monotonic_decreasing. - - Returns - ------- - bool - """ - from pandas import Index - - return Index(self).is_monotonic_decreasing - def memory_usage(self, deep=False): """ Memory usage of the values. diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 539f5515a2f8b..63c5c1732ea42 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1643,7 +1643,7 @@ def is_monotonic(self) -> bool: """ return self.is_monotonic_increasing - @property + @cache_readonly def is_monotonic_increasing(self) -> bool: """ Return if the index is monotonic increasing (only equal or @@ -1660,7 +1660,7 @@ def is_monotonic_increasing(self) -> bool: """ return self._engine.is_monotonic_increasing - @property + @cache_readonly def is_monotonic_decreasing(self) -> bool: """ Return if the index is monotonic decreasing (only equal or diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index 8038bc6bf1c72..eabc6521e8c27 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -333,7 +333,7 @@ def _format_with_header(self, header: List[str], na_rep: str = "NaN") -> List[st # -------------------------------------------------------------------- - @property + @cache_readonly def inferred_type(self) -> str: return "categorical" diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 5baa103a25d51..0747329643863 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -95,13 +95,13 @@ class DatetimeIndexOpsMixin(ExtensionIndex): _bool_ops: List[str] = [] _field_ops: List[str] = [] - # error: "Callable[[Any], Any]" has no attribute "fget" - hasnans = cache_readonly( - DatetimeLikeArrayMixin._hasnans.fget # type: ignore[attr-defined] - ) + @cache_readonly + def hasnans(self) -> bool: + return self._data._hasnans + _hasnans = hasnans # for index / array -agnostic code - @property + @cache_readonly def _is_all_dates(self) -> bool: return True diff --git a/pandas/core/indexes/datetimes.py b/pandas/core/indexes/datetimes.py index 479e2023a00cb..0560d52a31c77 100644 --- a/pandas/core/indexes/datetimes.py +++ b/pandas/core/indexes/datetimes.py @@ -799,7 +799,7 @@ def slice_indexer(self, start=None, end=None, step=None, kind=None): def is_type_compatible(self, typ) -> bool: return typ == self.inferred_type or typ == "datetime" - @property + @cache_readonly def inferred_type(self) -> str: # b/c datetime is represented as microseconds since the epoch, make # sure we can't have ambiguous indexing diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 93117fbc22752..4616e707d6129 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -385,7 +385,7 @@ def __contains__(self, key: Any) -> bool: def _multiindex(self) -> MultiIndex: return MultiIndex.from_arrays([self.left, self.right], names=["left", "right"]) - @cache_readonly + @property def values(self) -> IntervalArray: """ Return the IntervalIndex's data as an IntervalArray. @@ -414,7 +414,7 @@ def astype(self, dtype, copy: bool = True): return self._shallow_copy(new_values) return Index.astype(self, dtype, copy=copy) - @property + @cache_readonly def inferred_type(self) -> str: """Return a string of the type inferred from the values""" return "interval" @@ -1096,7 +1096,7 @@ def func(self, other, sort=sort): # -------------------------------------------------------------------- - @property + @cache_readonly def _is_all_dates(self) -> bool: """ This is False even when left/right contain datetime-like objects, diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index 0604f70316cfb..cca5075c43dd2 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -1228,7 +1228,7 @@ def memory_usage(self, deep: bool = False) -> int: # a tuple representation unnecessarily return self._nbytes(deep) - @cache_readonly + @property def nbytes(self) -> int: """ return the number of bytes in the underlying data """ return self._nbytes(False) @@ -1748,7 +1748,7 @@ def to_flat_index(self): """ return Index(self._values, tupleize_cols=False) - @property + @cache_readonly def _is_all_dates(self) -> bool: return False diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index 60a206a5344de..83d8caf6ac7f8 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -148,7 +148,7 @@ def _assert_safe_casting(cls, data, subarr): """ pass - @property + @cache_readonly def _is_all_dates(self) -> bool: """ Checks that all the labels are datetime objects. @@ -240,7 +240,7 @@ def __contains__(self, key) -> bool: except (OverflowError, TypeError, ValueError): return False - @property + @cache_readonly def inferred_type(self) -> str: """ Always 'integer' for ``Int64Index`` and ``UInt64Index`` @@ -335,7 +335,7 @@ class Float64Index(NumericIndex): _engine_type = libindex.Float64Engine _default_dtype = np.float64 - @property + @cache_readonly def inferred_type(self) -> str: """ Always 'floating' for ``Float64Index`` diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index ce2839ab9a8e1..11d4da269ba9c 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -419,7 +419,7 @@ def astype(self, dtype, copy: bool = True, how="start"): # TODO: should probably raise on `how` here, so we don't ignore it. return super().astype(dtype, copy=copy) - @property + @cache_readonly def is_full(self) -> bool: """ Returns True if this PeriodIndex is range-like in that all Periods @@ -432,7 +432,7 @@ def is_full(self) -> bool: values = self.asi8 return ((values[1:] - values[:-1]) < 2).all() - @property + @cache_readonly def inferred_type(self) -> str: # b/c data is represented as ints make sure we can't have ambiguous # indexing diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index 4a6bb11bda400..0fa00e90e5e45 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -274,7 +274,7 @@ def _step(self): ) return self.step - @cache_readonly + @property def nbytes(self) -> int: """ Return the number of bytes in the underlying data. @@ -310,11 +310,11 @@ def memory_usage(self, deep: bool = False) -> int: """ return self.nbytes - @property + @cache_readonly def dtype(self) -> np.dtype: return np.dtype(np.int64) - @property + @cache_readonly def is_unique(self) -> bool: """ return if the index has unique values """ return True diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 302fead8c8b0c..ed6412f8d55d3 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -4,7 +4,7 @@ from pandas._libs.tslibs import Timedelta, to_offset from pandas._typing import DtypeObj, Label from pandas.errors import InvalidIndexError -from pandas.util._decorators import doc +from pandas.util._decorators import cache_readonly, doc from pandas.core.dtypes.common import ( TD64NS_DTYPE, @@ -256,7 +256,7 @@ def _maybe_cast_slice_bound(self, label, side: str, kind): def is_type_compatible(self, typ) -> bool: return typ == self.inferred_type or typ == "timedelta" - @property + @cache_readonly def inferred_type(self) -> str: return "timedelta64" diff --git a/pandas/core/series.py b/pandas/core/series.py index 9bd41ca0e76db..31b38ca821d8f 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -1105,6 +1105,53 @@ def _set_value(self, label, value, takeable: bool = False): # ---------------------------------------------------------------------- # Unsorted + @property + def is_unique(self) -> bool: + """ + Return boolean if values in the object are unique. + + Returns + ------- + bool + """ + return self.nunique(dropna=False) == len(self) + + @property + def is_monotonic(self) -> bool: + """ + Return boolean if values in the object are + monotonic_increasing. + + Returns + ------- + bool + """ + from pandas import Index + + return Index(self).is_monotonic + + @property + def is_monotonic_increasing(self) -> bool: + """ + Alias for is_monotonic. + """ + # mypy complains if we alias directly + return self.is_monotonic + + @property + def is_monotonic_decreasing(self) -> bool: + """ + Return boolean if values in the object are + monotonic_decreasing. + + Returns + ------- + bool + """ + from pandas import Index + + return Index(self).is_monotonic_decreasing + @property def _is_mixed_type(self): return False From 65e2af68f32430af832a39027133a48d26cb55c6 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 8 Oct 2020 16:41:10 -0700 Subject: [PATCH 2/2] mypy fixup --- pandas/io/pytables.py | 3 +-- pandas/tests/extension/json/array.py | 7 ++++++- pandas/tests/extension/list/array.py | 7 ++++++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/pandas/io/pytables.py b/pandas/io/pytables.py index 3e3330fa4378f..8beb0d7c15b8d 100644 --- a/pandas/io/pytables.py +++ b/pandas/io/pytables.py @@ -2307,8 +2307,7 @@ def _get_atom(cls, values: ArrayLike) -> "Col": Get an appropriately typed and shaped pytables.Col object for values. """ dtype = values.dtype - # error: "ExtensionDtype" has no attribute "itemsize" - itemsize = dtype.itemsize # type: ignore[attr-defined] + itemsize = dtype.itemsize shape = values.shape if values.ndim == 1: diff --git a/pandas/tests/extension/json/array.py b/pandas/tests/extension/json/array.py index e3cdeb9c1951f..df912f3996f08 100644 --- a/pandas/tests/extension/json/array.py +++ b/pandas/tests/extension/json/array.py @@ -21,6 +21,8 @@ import numpy as np +from pandas.util._decorators import cache_readonly + from pandas.core.dtypes.common import pandas_dtype import pandas as pd @@ -45,7 +47,6 @@ def construct_array_type(cls) -> Type["JSONArray"]: class JSONArray(ExtensionArray): - dtype = JSONDtype() __array_priority__ = 1000 def __init__(self, values, dtype=None, copy=False): @@ -69,6 +70,10 @@ def _from_sequence(cls, scalars, dtype=None, copy=False): def _from_factorized(cls, values, original): return cls([UserDict(x) for x in values if x != ()]) + @cache_readonly + def dtype(self) -> JSONDtype: + return JSONDtype() + def __getitem__(self, item): if isinstance(item, numbers.Integral): return self.data[item] diff --git a/pandas/tests/extension/list/array.py b/pandas/tests/extension/list/array.py index d86f90e58d897..41cdadc966aae 100644 --- a/pandas/tests/extension/list/array.py +++ b/pandas/tests/extension/list/array.py @@ -10,6 +10,8 @@ import numpy as np +from pandas.util._decorators import cache_readonly + from pandas.core.dtypes.base import ExtensionDtype import pandas as pd @@ -34,7 +36,6 @@ def construct_array_type(cls) -> Type["ListArray"]: class ListArray(ExtensionArray): - dtype = ListDtype() __array_priority__ = 1000 def __init__(self, values, dtype=None, copy=False): @@ -51,6 +52,10 @@ def _from_sequence(cls, scalars, dtype=None, copy=False): data[:] = scalars return cls(data) + @cache_readonly + def dtype(self) -> ListDtype: + return ListDtype() + def __getitem__(self, item): if isinstance(item, numbers.Integral): return self.data[item]