From 91c4e956f97072282f911d96c556ddc1aa649bd9 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 27 Jan 2020 17:54:14 -0800 Subject: [PATCH] Backport PR #30860: REF: gradually move ExtensionIndex delegation to use inherit_names --- pandas/core/indexes/category.py | 30 ++++++++++---------- pandas/core/indexes/extension.py | 29 +++++++++++++++---- pandas/core/indexes/interval.py | 46 ++++++++----------------------- pandas/core/indexes/timedeltas.py | 9 +++--- 4 files changed, 56 insertions(+), 58 deletions(-) diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index 41072d4ce6a93..94dee95ab4154 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -27,7 +27,7 @@ import pandas.core.common as com import pandas.core.indexes.base as ibase from pandas.core.indexes.base import Index, _index_shared_docs, maybe_extract_name -from pandas.core.indexes.extension import ExtensionIndex +from pandas.core.indexes.extension import ExtensionIndex, inherit_names import pandas.core.missing as missing from pandas.core.ops import get_op_result_name @@ -35,11 +35,21 @@ _index_doc_kwargs.update(dict(target_klass="CategoricalIndex")) -@accessor.delegate_names( - delegate=Categorical, - accessors=["codes", "categories", "ordered"], - typ="property", - overwrite=True, +@inherit_names( + [ + "argsort", + "_internal_get_values", + "tolist", + "codes", + "categories", + "ordered", + "_reverse_indexer", + "searchsorted", + "is_dtype_equal", + "min", + "max", + ], + Categorical, ) @accessor.delegate_names( delegate=Categorical, @@ -52,14 +62,6 @@ "set_categories", "as_ordered", "as_unordered", - "min", - "max", - "is_dtype_equal", - "tolist", - "_internal_get_values", - "_reverse_indexer", - "searchsorted", - "argsort", ], typ="method", overwrite=True, diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 9ddc5c01030b1..6a10b3650293c 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -16,7 +16,7 @@ from pandas.core.ops import get_op_result_name -def inherit_from_data(name: str, delegate, cache: bool = False): +def inherit_from_data(name: str, delegate, cache: bool = False, wrap: bool = False): """ Make an alias for a method of the underlying ExtensionArray. @@ -27,6 +27,8 @@ def inherit_from_data(name: str, delegate, cache: bool = False): delegate : class cache : bool, default False Whether to convert wrapped properties into cache_readonly + wrap : bool, default False + Whether to wrap the inherited result in an Index. Returns ------- @@ -37,12 +39,23 @@ def inherit_from_data(name: str, delegate, cache: bool = False): if isinstance(attr, property): if cache: - method = cache_readonly(attr.fget) + + def cached(self): + return getattr(self._data, name) + + cached.__name__ = name + cached.__doc__ = attr.__doc__ + method = cache_readonly(cached) else: def fget(self): - return getattr(self._data, name) + result = getattr(self._data, name) + if wrap: + if isinstance(result, type(self._data)): + return type(self)._simple_new(result, name=self.name) + return Index(result, name=self.name) + return result def fset(self, value): setattr(self._data, name, value) @@ -60,6 +73,10 @@ def fset(self, value): def method(self, *args, **kwargs): result = attr(self._data, *args, **kwargs) + if wrap: + if isinstance(result, type(self._data)): + return type(self)._simple_new(result, name=self.name) + return Index(result, name=self.name) return result method.__name__ = name @@ -67,7 +84,7 @@ def method(self, *args, **kwargs): return method -def inherit_names(names: List[str], delegate, cache: bool = False): +def inherit_names(names: List[str], delegate, cache: bool = False, wrap: bool = False): """ Class decorator to pin attributes from an ExtensionArray to a Index subclass. @@ -76,11 +93,13 @@ def inherit_names(names: List[str], delegate, cache: bool = False): names : List[str] delegate : class cache : bool, default False + wrap : bool, default False + Whether to wrap the inherited result in an Index. """ def wrapper(cls): for name in names: - meth = inherit_from_data(name, delegate, cache=cache) + meth = inherit_from_data(name, delegate, cache=cache, wrap=wrap) setattr(cls, name, meth) return cls diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index d33ba52cc7524..6d3205bcf87a0 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -37,7 +37,6 @@ from pandas.core.dtypes.generic import ABCSeries from pandas.core.dtypes.missing import isna -from pandas.core import accessor from pandas.core.algorithms import take_1d from pandas.core.arrays.interval import IntervalArray, _interval_shared_docs import pandas.core.common as com @@ -183,31 +182,27 @@ def func(intvidx_self, other, sort=False): ), ) ) -@accessor.delegate_names( - delegate=IntervalArray, - accessors=["length", "size", "left", "right", "mid", "closed", "dtype"], - typ="property", - overwrite=True, -) -@accessor.delegate_names( - delegate=IntervalArray, - accessors=[ +@inherit_names(["set_closed", "to_tuples"], IntervalArray, wrap=True) +@inherit_names( + [ + "__len__", "__array__", "overlaps", "contains", - "__len__", - "set_closed", - "to_tuples", + "size", + "dtype", + "left", + "right", + "length", ], - typ="method", - overwrite=True, + IntervalArray, ) @inherit_names( - ["is_non_overlapping_monotonic", "mid", "_ndarray_values"], + ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed"], IntervalArray, cache=True, ) -class IntervalIndex(IntervalMixin, ExtensionIndex, accessor.PandasDelegate): +class IntervalIndex(IntervalMixin, ExtensionIndex): _typ = "intervalindex" _comparables = ["name"] _attributes = ["name", "closed"] @@ -218,8 +213,6 @@ class IntervalIndex(IntervalMixin, ExtensionIndex, accessor.PandasDelegate): # Immutable, so we are able to cache computations like isna in '_mask' _mask = None - _raw_inherit = {"__array__", "overlaps", "contains"} - # -------------------------------------------------------------------- # Constructors @@ -1176,21 +1169,6 @@ def is_all_dates(self) -> bool: # TODO: arithmetic operations - def _delegate_property_get(self, name, *args, **kwargs): - """ method delegation to the ._values """ - prop = getattr(self._data, name) - return prop # no wrapping for now - - def _delegate_method(self, name, *args, **kwargs): - """ method delegation to the ._data """ - method = getattr(self._data, name) - res = method(*args, **kwargs) - if is_scalar(res) or name in self._raw_inherit: - return res - if isinstance(res, IntervalArray): - return type(self)._simple_new(res, name=self.name) - return Index(res) - # GH#30817 until IntervalArray implements inequalities, get them from Index def __lt__(self, other): return Index.__lt__(self, other) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index 1f3182bc83e1d..c78020fba70c5 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -43,13 +43,12 @@ class TimedeltaDelegateMixin(DatetimelikeDelegateMixin): _raw_methods = {"to_pytimedelta", "sum", "std", "median", "_format_native_types"} _delegated_properties = TimedeltaArray._datetimelike_ops + list(_raw_properties) - _delegated_methods = ( - TimedeltaArray._datetimelike_methods - + list(_raw_methods) - + ["_box_values", "__neg__", "__pos__", "__abs__"] - ) + _delegated_methods = TimedeltaArray._datetimelike_methods + list(_raw_methods) +@inherit_names( + ["_box_values", "__neg__", "__pos__", "__abs__"], TimedeltaArray, wrap=True +) @inherit_names( [ "_bool_ops",