_available_if.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. from functools import update_wrapper, wraps
  2. from types import MethodType
  3. class _AvailableIfDescriptor:
  4. """Implements a conditional property using the descriptor protocol.
  5. Using this class to create a decorator will raise an ``AttributeError``
  6. if check(self) returns a falsey value. Note that if check raises an error
  7. this will also result in hasattr returning false.
  8. See https://docs.python.org/3/howto/descriptor.html for an explanation of
  9. descriptors.
  10. """
  11. def __init__(self, fn, check, attribute_name):
  12. self.fn = fn
  13. self.check = check
  14. self.attribute_name = attribute_name
  15. # update the docstring of the descriptor
  16. update_wrapper(self, fn)
  17. def __get__(self, obj, owner=None):
  18. attr_err = AttributeError(
  19. f"This {repr(owner.__name__)} has no attribute {repr(self.attribute_name)}"
  20. )
  21. if obj is not None:
  22. # delegate only on instances, not the classes.
  23. # this is to allow access to the docstrings.
  24. if not self.check(obj):
  25. raise attr_err
  26. out = MethodType(self.fn, obj)
  27. else:
  28. # This makes it possible to use the decorated method as an unbound method,
  29. # for instance when monkeypatching.
  30. @wraps(self.fn)
  31. def out(*args, **kwargs):
  32. if not self.check(args[0]):
  33. raise attr_err
  34. return self.fn(*args, **kwargs)
  35. return out
  36. def available_if(check):
  37. """An attribute that is available only if check returns a truthy value.
  38. Parameters
  39. ----------
  40. check : callable
  41. When passed the object with the decorated method, this should return
  42. a truthy value if the attribute is available, and either return False
  43. or raise an AttributeError if not available.
  44. Returns
  45. -------
  46. callable
  47. Callable makes the decorated method available if `check` returns
  48. a truthy value, otherwise the decorated method is unavailable.
  49. Examples
  50. --------
  51. >>> from sklearn.utils.metaestimators import available_if
  52. >>> class HelloIfEven:
  53. ... def __init__(self, x):
  54. ... self.x = x
  55. ...
  56. ... def _x_is_even(self):
  57. ... return self.x % 2 == 0
  58. ...
  59. ... @available_if(_x_is_even)
  60. ... def say_hello(self):
  61. ... print("Hello")
  62. ...
  63. >>> obj = HelloIfEven(1)
  64. >>> hasattr(obj, "say_hello")
  65. False
  66. >>> obj.x = 2
  67. >>> hasattr(obj, "say_hello")
  68. True
  69. >>> obj.say_hello()
  70. Hello
  71. """
  72. return lambda fn: _AvailableIfDescriptor(fn, check, attribute_name=fn.__name__)