From da79fe63c8f56b3b8f44bbb9f2a33740f7ef3385 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 08:40:38 -0800 Subject: [PATCH 1/8] REF: use inherit_names for wrapping --- pandas/core/indexes/extension.py | 21 +++++++++++++++++---- pandas/core/indexes/timedeltas.py | 2 +- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index efa5aa3c036c9..579a5a5a47c3d 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -14,7 +14,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. @@ -25,6 +25,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 ------- @@ -40,7 +42,12 @@ def inherit_from_data(name: str, delegate, cache: bool = False): 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) @@ -58,6 +65,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 @@ -65,7 +76,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. @@ -74,11 +85,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/timedeltas.py b/pandas/core/indexes/timedeltas.py index d1d8db0746cf8..0fb9b9dc6fe15 100644 --- a/pandas/core/indexes/timedeltas.py +++ b/pandas/core/indexes/timedeltas.py @@ -46,10 +46,10 @@ class TimedeltaDelegateMixin(DatetimelikeDelegateMixin): _delegated_methods = ( TimedeltaArray._datetimelike_methods + list(_raw_methods) - + ["_box_values", "__neg__", "__pos__", "__abs__"] ) +@inherit_names(["_box_values", "__neg__", "__pos__", "__abs__"], TimedeltaArray, wrap=True) @inherit_names( [ "_bool_ops", From 620013fc06191540b393513ff7d213375d49410a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 09:18:03 -0800 Subject: [PATCH 2/8] use inherit_names for IntervalIndex --- pandas/core/indexes/interval.py | 45 +++++++++------------------------ 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index caba33aa13e87..5be0c67e0a6bf 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 @@ -187,31 +186,28 @@ 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", + "closed", + "dtype", + "left", + "right", + "length", ], - typ="method", - overwrite=True, + IntervalArray, ) @inherit_names( ["is_non_overlapping_monotonic", "mid", "_ndarray_values"], IntervalArray, cache=True, ) -class IntervalIndex(IntervalMixin, ExtensionIndex, accessor.PandasDelegate): +class IntervalIndex(IntervalMixin, ExtensionIndex): _typ = "intervalindex" _comparables = ["name"] _attributes = ["name", "closed"] @@ -222,8 +218,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 @@ -1180,21 +1174,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) - @classmethod def _add_comparison_methods(cls): """ add in comparison methods """ From 4b15d32eb18dddf5e9c3a23e0e8a6b9ef615d60c Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 10:27:50 -0800 Subject: [PATCH 3/8] REF: use inherit_names for CategoricalIndex --- pandas/core/indexes/category.py | 34 +++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index 77165034f313d..85a022f09c995 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -29,7 +29,11 @@ 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, make_wrapped_comparison_op +from pandas.core.indexes.extension import ( + ExtensionIndex, + inherit_names, + make_wrapped_comparison_op, +) import pandas.core.missing as missing from pandas.core.ops import get_op_result_name @@ -37,11 +41,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, @@ -54,14 +68,6 @@ "set_categories", "as_ordered", "as_unordered", - "min", - "max", - "is_dtype_equal", - "tolist", - "_internal_get_values", - "_reverse_indexer", - "searchsorted", - "argsort", ], typ="method", overwrite=True, From 813bd91ba67f9b561a3d25c1d2d9a774a818e53a Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Thu, 9 Jan 2020 11:26:32 -0800 Subject: [PATCH 4/8] blackify --- pandas/core/indexes/timedeltas.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pandas/core/indexes/timedeltas.py b/pandas/core/indexes/timedeltas.py index f29b2e75d28cf..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) - ) + _delegated_methods = TimedeltaArray._datetimelike_methods + list(_raw_methods) -@inherit_names(["_box_values", "__neg__", "__pos__", "__abs__"], TimedeltaArray, wrap=True) +@inherit_names( + ["_box_values", "__neg__", "__pos__", "__abs__"], TimedeltaArray, wrap=True +) @inherit_names( [ "_bool_ops", From 9f5a45e026e7ca20d5b5ae7ea8ae5999fc66c2db Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 27 Jan 2020 08:15:25 -0800 Subject: [PATCH 5/8] make closed cached --- pandas/core/indexes/interval.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 68e8df2f12ac7..3e792eba6ad8e 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -193,7 +193,6 @@ def func(intvidx_self, other, sort=False): "overlaps", "contains", "size", - "closed", "dtype", "left", "right", @@ -202,7 +201,7 @@ def func(intvidx_self, other, sort=False): IntervalArray, ) @inherit_names( - ["is_non_overlapping_monotonic", "mid", "_ndarray_values"], + ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed"], IntervalArray, cache=True, ) From def08b7f2356a71f4296c1dca9ebca3f4783b254 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 27 Jan 2020 08:50:01 -0800 Subject: [PATCH 6/8] add _closed to fix doctest --- pandas/core/indexes/interval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 3e792eba6ad8e..396e40ad9cfca 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -201,7 +201,7 @@ def func(intvidx_self, other, sort=False): IntervalArray, ) @inherit_names( - ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed"], + ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed", "_closed"], IntervalArray, cache=True, ) From 4f44d9ce2f6a3c93faa857c42b9b14d2c8e4a0b5 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 27 Jan 2020 09:09:00 -0800 Subject: [PATCH 7/8] revert --- pandas/core/indexes/interval.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 396e40ad9cfca..3e792eba6ad8e 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -201,7 +201,7 @@ def func(intvidx_self, other, sort=False): IntervalArray, ) @inherit_names( - ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed", "_closed"], + ["is_non_overlapping_monotonic", "mid", "_ndarray_values", "closed"], IntervalArray, cache=True, ) From c9fdf046341186054c3f882c84d30d81d30dd763 Mon Sep 17 00:00:00 2001 From: jbrockmendel Date: Mon, 27 Jan 2020 11:48:50 -0800 Subject: [PATCH 8/8] Fix for _closed --- pandas/core/indexes/extension.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/extension.py b/pandas/core/indexes/extension.py index 09d312723f278..6a10b3650293c 100644 --- a/pandas/core/indexes/extension.py +++ b/pandas/core/indexes/extension.py @@ -39,7 +39,13 @@ def inherit_from_data(name: str, delegate, cache: bool = False, wrap: bool = Fal 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: