-
-
Notifications
You must be signed in to change notification settings - Fork 232
/
metaestimators.py
98 lines (82 loc) · 3.38 KB
/
metaestimators.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# -*- coding: utf-8 -*-
#
# Author: Taylor Smith <taylor.smith@alkaline-ml.com>
#
# 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)