Skip to content

Implement delegate_names to allow decorating delegated attributes #22599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions pandas/core/accessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,38 @@ def f(self, *args, **kwargs):
setattr(cls, name, f)


def delegate_names(delegate, accessors, typ, overwrite=False):
"""
Add delegated names to a class using a class decorator. This provides
an alternative usage to directly calling `_add_delegate_accessors`
below a class definition.

Parameters
----------
delegate : the class to get methods/properties & doc-strings
acccessors : string list of accessors to add
typ : 'property' or 'method'
overwrite : boolean, default False
overwrite the method/property in the target class if it exists

Returns
-------
decorator

Examples
--------
@delegate_names(Categorical, ["categories", "ordered"], "property")
class CategoricalAccessor(PandasDelegate):
[...]
"""
def add_delegate_accessors(cls):
cls._add_delegate_accessors(delegate, accessors, typ,
overwrite=overwrite)
return cls

return add_delegate_accessors


# Ported with modifications from xarray
# https://github.com/pydata/xarray/blob/master/xarray/core/extensions.py
# 1. We don't need to catch and re-raise AttributeErrors as RuntimeErrors
Expand Down
20 changes: 10 additions & 10 deletions pandas/core/arrays/categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
is_dict_like)

from pandas.core.algorithms import factorize, take_1d, unique1d, take
from pandas.core.accessor import PandasDelegate
from pandas.core.accessor import PandasDelegate, delegate_names
from pandas.core.base import (PandasObject,
NoNewAttributesMixin, _shared_docs)
import pandas.core.common as com
Expand Down Expand Up @@ -2365,6 +2365,15 @@ def isin(self, values):
# The Series.cat accessor


@delegate_names(delegate=Categorical,
accessors=["categories", "ordered"],
typ="property")
@delegate_names(delegate=Categorical,
accessors=["rename_categories", "reorder_categories",
"add_categories", "remove_categories",
"remove_unused_categories", "set_categories",
"as_ordered", "as_unordered"],
typ="method")
class CategoricalAccessor(PandasDelegate, PandasObject, NoNewAttributesMixin):
"""
Accessor object for categorical properties of the Series values.
Expand Down Expand Up @@ -2424,15 +2433,6 @@ def _delegate_method(self, name, *args, **kwargs):
return Series(res, index=self.index, name=self.name)


CategoricalAccessor._add_delegate_accessors(delegate=Categorical,
accessors=["categories",
"ordered"],
typ='property')
CategoricalAccessor._add_delegate_accessors(delegate=Categorical, accessors=[
"rename_categories", "reorder_categories", "add_categories",
"remove_categories", "remove_unused_categories", "set_categories",
"as_ordered", "as_unordered"], typ='method')

# utility routines


Expand Down
50 changes: 19 additions & 31 deletions pandas/core/indexes/accessors.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
is_timedelta64_dtype, is_categorical_dtype,
is_list_like)

from pandas.core.accessor import PandasDelegate
from pandas.core.accessor import PandasDelegate, delegate_names
from pandas.core.base import NoNewAttributesMixin, PandasObject
from pandas.core.indexes.datetimes import DatetimeIndex
from pandas.core.indexes.period import PeriodIndex
Expand Down Expand Up @@ -110,6 +110,12 @@ def _delegate_method(self, name, *args, **kwargs):
return result


@delegate_names(delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_ops,
typ="property")
@delegate_names(delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_methods,
typ="method")
class DatetimeProperties(Properties):
"""
Accessor object for datetimelike properties of the Series values.
Expand Down Expand Up @@ -175,16 +181,12 @@ def freq(self):
return self._get_values().inferred_freq


DatetimeProperties._add_delegate_accessors(
delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_ops,
typ='property')
DatetimeProperties._add_delegate_accessors(
delegate=DatetimeIndex,
accessors=DatetimeIndex._datetimelike_methods,
typ='method')


@delegate_names(delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_ops,
typ="property")
@delegate_names(delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_methods,
typ="method")
class TimedeltaProperties(Properties):
"""
Accessor object for datetimelike properties of the Series values.
Expand Down Expand Up @@ -268,16 +270,12 @@ def freq(self):
return self._get_values().inferred_freq


TimedeltaProperties._add_delegate_accessors(
delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_ops,
typ='property')
TimedeltaProperties._add_delegate_accessors(
delegate=TimedeltaIndex,
accessors=TimedeltaIndex._datetimelike_methods,
typ='method')


@delegate_names(delegate=PeriodIndex,
accessors=PeriodIndex._datetimelike_ops,
typ="property")
@delegate_names(delegate=PeriodIndex,
accessors=PeriodIndex._datetimelike_methods,
typ="method")
class PeriodProperties(Properties):
"""
Accessor object for datetimelike properties of the Series values.
Expand All @@ -293,16 +291,6 @@ class PeriodProperties(Properties):
"""


PeriodProperties._add_delegate_accessors(
delegate=PeriodIndex,
accessors=PeriodIndex._datetimelike_ops,
typ='property')
PeriodProperties._add_delegate_accessors(
delegate=PeriodIndex,
accessors=PeriodIndex._datetimelike_methods,
typ='method')


class CombinedDatetimelikeProperties(DatetimeProperties, TimedeltaProperties):

def __new__(cls, data):
Expand Down
27 changes: 11 additions & 16 deletions pandas/core/indexes/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@
_index_doc_kwargs.update(dict(target_klass='CategoricalIndex'))


@accessor.delegate_names(
delegate=Categorical,
accessors=["rename_categories",
"reorder_categories",
"add_categories",
"remove_categories",
"remove_unused_categories",
"set_categories",
"as_ordered", "as_unordered",
"min", "max"],
typ='method', overwrite=True)
class CategoricalIndex(Index, accessor.PandasDelegate):
"""

Expand Down Expand Up @@ -835,24 +846,8 @@ def _delegate_method(self, name, *args, **kwargs):
return res
return CategoricalIndex(res, name=self.name)

@classmethod
def _add_accessors(cls):
""" add in Categorical accessor methods """

CategoricalIndex._add_delegate_accessors(
delegate=Categorical, accessors=["rename_categories",
"reorder_categories",
"add_categories",
"remove_categories",
"remove_unused_categories",
"set_categories",
"as_ordered", "as_unordered",
"min", "max"],
typ='method', overwrite=True)


CategoricalIndex._add_numeric_methods_add_sub_disabled()
CategoricalIndex._add_numeric_methods_disabled()
CategoricalIndex._add_logical_methods_disabled()
CategoricalIndex._add_comparison_methods()
CategoricalIndex._add_accessors()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slightly -0 here as I like the _add_* flow. but ok