# -*- coding: utf-8 -*- # # Author: Taylor Smith # # Metaestimators for the ARIMA class. These classes are derived from the # sklearn metaestimators, but adapted for more specific use with pmdarima. from operator import attrgetter from functools import update_wrapper __all__ = [ 'if_has_delegate' ] class _IffHasDelegate(object): """Implements a conditional property using the descriptor protocol. Using this class to create a decorator will raise an ``AttributeError`` if none of the delegates (specified in ``delegate_names``) is an attribute of the base object or the first found delegate does not have an attribute ``attribute_name``. This allows ducktyping of the decorated method based on ``delegate.attribute_name``. Here ``delegate`` is the first item in ``delegate_names`` for which ``hasattr(object, delegate) is True``. See https://docs.python.org/3/howto/descriptor.html for an explanation of descriptors. """ def __init__(self, fn, delegate_names): self.fn = fn self.delegate_names = delegate_names # update the docstring of the descriptor update_wrapper(self, fn) def __get__(self, obj, type=None): # raise an AttributeError if the attribute is not present on the object if obj is not None: # delegate only on instances, not the classes. # this is to allow access to the docstrings. for delegate_name in self.delegate_names: try: attrgetter(delegate_name)(obj) except AttributeError: continue else: break else: attrgetter(self.delegate_names[-1])(obj) # lambda, but not partial, allows help() to work with update_wrapper out = (lambda *args, **kwargs: self.fn(obj, *args, **kwargs)) # update the docstring of the returned function update_wrapper(out, self.fn) return out def if_has_delegate(delegate): """Wrap a delegated instance attribute function. Creates a decorator for methods that are delegated in the presence of a results wrapper. This enables duck-typing by ``hasattr`` returning True according to the sub-estimator. This function was adapted from scikit-learn, which defines ``if_delegate_has_method``, but operates differently by injecting methods not based on method presence, but by delegate presence. Examples -------- >>> from pmdarima.utils.metaestimators import if_has_delegate >>> >>> class A(object): ... @if_has_delegate('d') ... def func(self): ... return True >>> >>> a = A() >>> # the delegate does not exist yet >>> assert not hasattr(a, 'func') >>> # inject the attribute >>> a.d = None >>> assert hasattr(a, 'func') and a.func() Parameters ---------- delegate : string, list of strings or tuple of strings Name of the sub-estimator that can be accessed as an attribute of the base object. If a list or a tuple of names are provided, the first sub-estimator that is an attribute of the base object will be used. """ if isinstance(delegate, list): delegate = tuple(delegate) if not isinstance(delegate, tuple): delegate = (delegate,) return lambda fn: _IffHasDelegate(fn, delegate)