diff --git a/pandas/core/accessor.py b/pandas/core/accessor.py index c8476841bfce4..661cd5b74604c 100644 --- a/pandas/core/accessor.py +++ b/pandas/core/accessor.py @@ -93,38 +93,106 @@ def _add_delegate_accessors(cls, delegate, accessors, typ, overwrite : boolean, default False overwrite the method/property in the target class if it exists """ + for name in accessors: - def _create_delegator_property(name): + if typ == 'property': + f = Delegator.create_delegator_property(name, delegate) + else: + f = Delegator.create_delegator_method(name, delegate) - def _getter(self): - return self._delegate_property_get(name) + # don't overwrite existing methods/properties + if overwrite or not hasattr(cls, name): + setattr(cls, name, f) - def _setter(self, new_values): - return self._delegate_property_set(name, new_values) - _getter.__name__ = name - _setter.__name__ = name +class Delegator(object): + """ Delegator class contains methods that are used by PandasDelegate + and Accessor subclasses, but that so not ultimately belong in + the namespaces of user-facing classes. - return property(fget=_getter, fset=_setter, - doc=getattr(delegate, name).__doc__) + Many of these methods *could* be module-level functions, but are + retained as staticmethods for organization purposes. + """ - def _create_delegator_method(name): + @staticmethod + def create_delegator_property(name, delegate): + # Note: we really only need the `delegate` here for the docstring - def f(self, *args, **kwargs): - return self._delegate_method(name, *args, **kwargs) + def _getter(self): + return self._delegate_property_get(name) - f.__name__ = name - f.__doc__ = getattr(delegate, name).__doc__ + def _setter(self, new_values): + return self._delegate_property_set(name, new_values) + # TODO: not hit in tests; not sure this is something we + # really want anyway - return f + _getter.__name__ = name + _setter.__name__ = name + _doc = getattr(delegate, name).__doc__ + return property(fget=_getter, fset=_setter, doc=_doc) - for name in accessors: + @staticmethod + def create_delegator_method(name, delegate): + # Note: we really only need the `delegate` here for the docstring - if typ == 'property': - f = _create_delegator_property(name) - else: - f = _create_delegator_method(name) + def func(self, *args, **kwargs): + return self._delegate_method(name, *args, **kwargs) - # don't overwrite existing methods/properties - if overwrite or not hasattr(cls, name): - setattr(cls, name, f) + func.__name__ = name + func.__doc__ = getattr(delegate, name).__doc__ + return func + + @staticmethod + def delegate_names(delegate, accessors, typ, overwrite=False): + """ + delegate_names decorates class definitions, e.g: + + @delegate_names(Categorical, ["categories", "ordered"], "property") + class CategoricalAccessor(PandasDelegate): + + @classmethod + def _make_accessor(cls, data): + [...] + + + The motivation is that we would like to keep as much of a class's + internals inside the class definition. For things that we cannot + keep directly in the class definition, a decorator is more directly + tied to the definition than a method call outside the definition. + + """ + # Note: we really only need the `delegate` here for the docstring + + def add_delegate_accessors(cls): + """ + 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 + """ + for name in accessors: + if typ == "property": + func = Delegator.create_delegator_property(name, delegate) + else: + func = Delegator.create_delegator_method(name, delegate) + + # don't overwrite existing methods/properties unless + # specifically told to do so + if overwrite or not hasattr(cls, name): + setattr(cls, name, func) + + return cls + + return add_delegate_accessors + + +wrap_delegate_names = Delegator.delegate_names +# TODO: the `delegate` arg to `wrap_delegate_names` is really only relevant +# for a docstring. It'd be nice if we didn't require it and could duck-type +# instead.