some new features
This commit is contained in:
@ -0,0 +1,93 @@
|
||||
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 the docstring of the descriptor
|
||||
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:
|
||||
# delegate only on instances, not the classes.
|
||||
# this is to allow access to the docstrings.
|
||||
self._check(obj, owner=owner)
|
||||
out = MethodType(self.fn, obj)
|
||||
|
||||
else:
|
||||
# This makes it possible to use the decorated method as an unbound method,
|
||||
# for instance when monkeypatching.
|
||||
@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__)
|
||||
Reference in New Issue
Block a user