diff --git a/pandas/core/accessor.py b/pandas/core/accessor.py index 9f8556d1e6961..c8476841bfce4 100644 --- a/pandas/core/accessor.py +++ b/pandas/core/accessor.py @@ -5,6 +5,7 @@ that can be mixed into or pinned onto other pandas classes. """ +from pandas.core.common import AbstractMethodError class DirNamesMixin(object): @@ -33,3 +34,97 @@ def __dir__(self): rv = set(dir(type(self))) rv = (rv - self._dir_deletions()) | self._dir_additions() return sorted(rv) + + +class AccessorProperty(object): + """Descriptor for implementing accessor properties like Series.str + """ + + def __init__(self, accessor_cls, construct_accessor=None): + self.accessor_cls = accessor_cls + self.construct_accessor = (construct_accessor or + accessor_cls._make_accessor) + self.__doc__ = accessor_cls.__doc__ + + def __get__(self, instance, owner=None): + if instance is None: + # this ensures that Series.str. is well defined + return self.accessor_cls + return self.construct_accessor(instance) + + def __set__(self, instance, value): + raise AttributeError("can't set attribute") + + def __delete__(self, instance): + raise AttributeError("can't delete attribute") + + +class PandasDelegate(object): + """ an abstract base class for delegating methods/properties """ + + @classmethod + def _make_accessor(cls, data): + raise AbstractMethodError("_make_accessor should be implemented" + "by subclass and return an instance" + "of `cls`.") + + def _delegate_property_get(self, name, *args, **kwargs): + raise TypeError("You cannot access the " + "property {name}".format(name=name)) + + def _delegate_property_set(self, name, value, *args, **kwargs): + raise TypeError("The property {name} cannot be set".format(name=name)) + + def _delegate_method(self, name, *args, **kwargs): + raise TypeError("You cannot call method {name}".format(name=name)) + + @classmethod + def _add_delegate_accessors(cls, delegate, accessors, typ, + overwrite=False): + """ + add accessors to cls from the delegate class + + Parameters + ---------- + cls : the class to add the methods/properties to + 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 + """ + + def _create_delegator_property(name): + + def _getter(self): + return self._delegate_property_get(name) + + def _setter(self, new_values): + return self._delegate_property_set(name, new_values) + + _getter.__name__ = name + _setter.__name__ = name + + return property(fget=_getter, fset=_setter, + doc=getattr(delegate, name).__doc__) + + def _create_delegator_method(name): + + def f(self, *args, **kwargs): + return self._delegate_method(name, *args, **kwargs) + + f.__name__ = name + f.__doc__ = getattr(delegate, name).__doc__ + + return f + + for name in accessors: + + if typ == 'property': + f = _create_delegator_property(name) + else: + f = _create_delegator_method(name) + + # don't overwrite existing methods/properties + if overwrite or not hasattr(cls, name): + setattr(cls, name, f) diff --git a/pandas/core/base.py b/pandas/core/base.py index be021f3621c73..19f6728642645 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -153,100 +153,6 @@ def __setattr__(self, key, value): object.__setattr__(self, key, value) -class PandasDelegate(PandasObject): - """ an abstract base class for delegating methods/properties """ - - @classmethod - def _make_accessor(cls, data): - raise AbstractMethodError("_make_accessor should be implemented" - "by subclass and return an instance" - "of `cls`.") - - def _delegate_property_get(self, name, *args, **kwargs): - raise TypeError("You cannot access the " - "property {name}".format(name=name)) - - def _delegate_property_set(self, name, value, *args, **kwargs): - raise TypeError("The property {name} cannot be set".format(name=name)) - - def _delegate_method(self, name, *args, **kwargs): - raise TypeError("You cannot call method {name}".format(name=name)) - - @classmethod - def _add_delegate_accessors(cls, delegate, accessors, typ, - overwrite=False): - """ - add accessors to cls from the delegate class - - Parameters - ---------- - cls : the class to add the methods/properties to - 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 - """ - - def _create_delegator_property(name): - - def _getter(self): - return self._delegate_property_get(name) - - def _setter(self, new_values): - return self._delegate_property_set(name, new_values) - - _getter.__name__ = name - _setter.__name__ = name - - return property(fget=_getter, fset=_setter, - doc=getattr(delegate, name).__doc__) - - def _create_delegator_method(name): - - def f(self, *args, **kwargs): - return self._delegate_method(name, *args, **kwargs) - - f.__name__ = name - f.__doc__ = getattr(delegate, name).__doc__ - - return f - - for name in accessors: - - if typ == 'property': - f = _create_delegator_property(name) - else: - f = _create_delegator_method(name) - - # don't overwrite existing methods/properties - if overwrite or not hasattr(cls, name): - setattr(cls, name, f) - - -class AccessorProperty(object): - """Descriptor for implementing accessor properties like Series.str - """ - - def __init__(self, accessor_cls, construct_accessor=None): - self.accessor_cls = accessor_cls - self.construct_accessor = (construct_accessor or - accessor_cls._make_accessor) - self.__doc__ = accessor_cls.__doc__ - - def __get__(self, instance, owner=None): - if instance is None: - # this ensures that Series.str. is well defined - return self.accessor_cls - return self.construct_accessor(instance) - - def __set__(self, instance, value): - raise AttributeError("can't set attribute") - - def __delete__(self, instance): - raise AttributeError("can't delete attribute") - - class GroupByError(Exception): pass diff --git a/pandas/core/categorical.py b/pandas/core/categorical.py index 98d6d7a68017a..743bae2fd2848 100644 --- a/pandas/core/categorical.py +++ b/pandas/core/categorical.py @@ -30,7 +30,8 @@ from pandas.core.common import is_null_slice, _maybe_box_datetimelike from pandas.core.algorithms import factorize, take_1d, unique1d -from pandas.core.base import (PandasObject, PandasDelegate, +from pandas.core.accessor import PandasDelegate +from pandas.core.base import (PandasObject, NoNewAttributesMixin, _shared_docs) import pandas.core.common as com from pandas.core.missing import interpolate_2d @@ -2065,7 +2066,7 @@ def repeat(self, repeats, *args, **kwargs): # The Series.cat accessor -class CategoricalAccessor(PandasDelegate, NoNewAttributesMixin): +class CategoricalAccessor(PandasDelegate, PandasObject, NoNewAttributesMixin): """ Accessor object for categorical properties of the Series values. diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 346eeb8d2642c..899ae99d5deb1 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -90,7 +90,7 @@ from pandas.core.indexes.datetimes import DatetimeIndex from pandas.core.indexes.timedeltas import TimedeltaIndex -import pandas.core.base as base +from pandas.core import accessor import pandas.core.common as com import pandas.core.nanops as nanops import pandas.core.ops as ops @@ -5897,7 +5897,8 @@ def isin(self, values): # ---------------------------------------------------------------------- # Add plotting methods to DataFrame - plot = base.AccessorProperty(gfx.FramePlotMethods, gfx.FramePlotMethods) + plot = accessor.AccessorProperty(gfx.FramePlotMethods, + gfx.FramePlotMethods) hist = gfx.hist_frame boxplot = gfx.boxplot_frame diff --git a/pandas/core/indexes/accessors.py b/pandas/core/indexes/accessors.py index 88297ac70984d..2176338574304 100644 --- a/pandas/core/indexes/accessors.py +++ b/pandas/core/indexes/accessors.py @@ -11,7 +11,8 @@ is_timedelta64_dtype, is_categorical_dtype, is_list_like) -from pandas.core.base import PandasDelegate, NoNewAttributesMixin +from pandas.core.accessor import PandasDelegate +from pandas.core.base import NoNewAttributesMixin, PandasObject from pandas.core.indexes.datetimes import DatetimeIndex from pandas._libs.period import IncompatibleFrequency # noqa from pandas.core.indexes.period import PeriodIndex @@ -81,7 +82,7 @@ def maybe_to_datetimelike(data, copy=False): "datetimelike index".format(type(data))) -class Properties(PandasDelegate, NoNewAttributesMixin): +class Properties(PandasDelegate, PandasObject, NoNewAttributesMixin): def __init__(self, values, index, name, orig=None): self.values = values diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 562a758f83edc..f28ff9697e517 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -57,7 +57,7 @@ import pandas.core.sorting as sorting from pandas.io.formats.printing import pprint_thing from pandas.core.ops import _comp_method_OBJECT_ARRAY -from pandas.core import strings +from pandas.core import strings, accessor from pandas.core.config import get_option @@ -159,7 +159,7 @@ class Index(IndexOpsMixin, PandasObject): _accessors = frozenset(['str']) # String Methods - str = base.AccessorProperty(strings.StringMethods) + str = accessor.AccessorProperty(strings.StringMethods) def __new__(cls, data=None, dtype=None, copy=False, name=None, fastpath=False, tupleize_cols=True, **kwargs): diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index 9a055afccd799..8b680127723c3 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -19,6 +19,7 @@ from pandas.util._decorators import Appender, cache_readonly from pandas.core.config import get_option from pandas.core.indexes.base import Index, _index_shared_docs +from pandas.core import accessor import pandas.core.base as base import pandas.core.missing as missing import pandas.core.indexes.base as ibase @@ -27,7 +28,7 @@ _index_doc_kwargs.update(dict(target_klass='CategoricalIndex')) -class CategoricalIndex(Index, base.PandasDelegate): +class CategoricalIndex(Index, accessor.PandasDelegate): """ Immutable Index implementing an ordered, sliceable set. CategoricalIndex diff --git a/pandas/core/series.py b/pandas/core/series.py index ea9aeefe3b665..db8ee2529ef57 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -62,6 +62,7 @@ from pandas.compat import zip, u, OrderedDict, StringIO from pandas.compat.numpy import function as nv +from pandas.core import accessor import pandas.core.ops as ops import pandas.core.algorithms as algorithms @@ -2901,19 +2902,19 @@ def to_period(self, freq=None, copy=True): # ------------------------------------------------------------------------- # Datetimelike delegation methods - dt = base.AccessorProperty(CombinedDatetimelikeProperties) + dt = accessor.AccessorProperty(CombinedDatetimelikeProperties) # ------------------------------------------------------------------------- # Categorical methods - cat = base.AccessorProperty(CategoricalAccessor) + cat = accessor.AccessorProperty(CategoricalAccessor) # String Methods - str = base.AccessorProperty(strings.StringMethods) + str = accessor.AccessorProperty(strings.StringMethods) # ---------------------------------------------------------------------- # Add plotting methods to Series - plot = base.AccessorProperty(gfx.SeriesPlotMethods, - gfx.SeriesPlotMethods) + plot = accessor.AccessorProperty(gfx.SeriesPlotMethods, + gfx.SeriesPlotMethods) hist = gfx.hist_series diff --git a/pandas/tests/test_base.py b/pandas/tests/test_base.py index 38d78b12b31aa..5bfd8eb7eae24 100644 --- a/pandas/tests/test_base.py +++ b/pandas/tests/test_base.py @@ -18,7 +18,8 @@ CategoricalIndex, Timestamp) from pandas.compat import StringIO, PYPY, long from pandas.compat.numpy import np_array_datetime64_compat -from pandas.core.base import PandasDelegate, NoNewAttributesMixin +from pandas.core.accessor import PandasDelegate +from pandas.core.base import PandasObject, NoNewAttributesMixin from pandas.core.indexes.datetimelike import DatetimeIndexOpsMixin from pandas._libs.tslib import iNaT @@ -105,7 +106,7 @@ def bar(self, *args, **kwargs): """ a test bar method """ pass - class Delegate(PandasDelegate): + class Delegate(PandasDelegate, PandasObject): def __init__(self, obj): self.obj = obj