|
|
|
|
|
|
|
from functools import update_wrapper, wraps |
|
from types import MethodType |
|
|
|
|
|
class _AvailableIfDescriptor: |
|
"""Implements a conditional property using the descriptor protocol. |
|
|
|
Using this class to create a decorator will raise an ``AttributeError`` |
|
if check(self) returns a falsey value. Note that if check raises an error |
|
this will also result in hasattr returning false. |
|
|
|
See https://docs.python.org/3/howto/descriptor.html for an explanation of |
|
descriptors. |
|
""" |
|
|
|
def __init__(self, fn, check, attribute_name): |
|
self.fn = fn |
|
self.check = check |
|
self.attribute_name = attribute_name |
|
|
|
|
|
update_wrapper(self, fn) |
|
|
|
def _check(self, obj, owner): |
|
attr_err_msg = ( |
|
f"This {repr(owner.__name__)} has no attribute {repr(self.attribute_name)}" |
|
) |
|
try: |
|
check_result = self.check(obj) |
|
except Exception as e: |
|
raise AttributeError(attr_err_msg) from e |
|
|
|
if not check_result: |
|
raise AttributeError(attr_err_msg) |
|
|
|
def __get__(self, obj, owner=None): |
|
if obj is not None: |
|
|
|
|
|
self._check(obj, owner=owner) |
|
out = MethodType(self.fn, obj) |
|
|
|
else: |
|
|
|
|
|
@wraps(self.fn) |
|
def out(*args, **kwargs): |
|
self._check(args[0], owner=owner) |
|
return self.fn(*args, **kwargs) |
|
|
|
return out |
|
|
|
|
|
def available_if(check): |
|
"""An attribute that is available only if check returns a truthy value. |
|
|
|
Parameters |
|
---------- |
|
check : callable |
|
When passed the object with the decorated method, this should return |
|
a truthy value if the attribute is available, and either return False |
|
or raise an AttributeError if not available. |
|
|
|
Returns |
|
------- |
|
callable |
|
Callable makes the decorated method available if `check` returns |
|
a truthy value, otherwise the decorated method is unavailable. |
|
|
|
Examples |
|
-------- |
|
>>> from sklearn.utils.metaestimators import available_if |
|
>>> class HelloIfEven: |
|
... def __init__(self, x): |
|
... self.x = x |
|
... |
|
... def _x_is_even(self): |
|
... return self.x % 2 == 0 |
|
... |
|
... @available_if(_x_is_even) |
|
... def say_hello(self): |
|
... print("Hello") |
|
... |
|
>>> obj = HelloIfEven(1) |
|
>>> hasattr(obj, "say_hello") |
|
False |
|
>>> obj.x = 2 |
|
>>> hasattr(obj, "say_hello") |
|
True |
|
>>> obj.say_hello() |
|
Hello |
|
""" |
|
return lambda fn: _AvailableIfDescriptor(fn, check, attribute_name=fn.__name__) |
|
|