_helpers_c.pyx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. # cython: language_level=3, freethreading_compatible=True
  2. from types import GenericAlias
  3. cdef _sentinel = object()
  4. cdef class under_cached_property:
  5. """Use as a class method decorator. It operates almost exactly like
  6. the Python `@property` decorator, but it puts the result of the
  7. method it decorates into the instance dict after the first call,
  8. effectively replacing the function it decorates with an instance
  9. variable. It is, in Python parlance, a data descriptor.
  10. """
  11. cdef readonly object wrapped
  12. cdef object name
  13. def __init__(self, wrapped):
  14. self.wrapped = wrapped
  15. self.name = wrapped.__name__
  16. @property
  17. def __doc__(self):
  18. return self.wrapped.__doc__
  19. def __get__(self, inst, owner):
  20. if inst is None:
  21. return self
  22. cdef dict cache = inst._cache
  23. val = cache.get(self.name, _sentinel)
  24. if val is _sentinel:
  25. val = self.wrapped(inst)
  26. cache[self.name] = val
  27. return val
  28. def __set__(self, inst, value):
  29. raise AttributeError("cached property is read-only")
  30. __class_getitem__ = classmethod(GenericAlias)
  31. cdef class cached_property:
  32. """Use as a class method decorator. It operates almost exactly like
  33. the Python `@property` decorator, but it puts the result of the
  34. method it decorates into the instance dict after the first call,
  35. effectively replacing the function it decorates with an instance
  36. variable. It is, in Python parlance, a data descriptor.
  37. """
  38. cdef readonly object func
  39. cdef object name
  40. def __init__(self, func):
  41. self.func = func
  42. self.name = None
  43. @property
  44. def __doc__(self):
  45. return self.func.__doc__
  46. def __set_name__(self, owner, name):
  47. if self.name is None:
  48. self.name = name
  49. elif name != self.name:
  50. raise TypeError(
  51. "Cannot assign the same cached_property to two different names "
  52. f"({self.name!r} and {name!r})."
  53. )
  54. def __get__(self, inst, owner):
  55. if inst is None:
  56. return self
  57. if self.name is None:
  58. raise TypeError(
  59. "Cannot use cached_property instance"
  60. " without calling __set_name__ on it.")
  61. cdef dict cache = inst.__dict__
  62. val = cache.get(self.name, _sentinel)
  63. if val is _sentinel:
  64. val = self.func(inst)
  65. cache[self.name] = val
  66. return val
  67. __class_getitem__ = classmethod(GenericAlias)