_helpers_c.pyx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. # cython: language_level=3, freethreading_compatible=True
  2. from types import GenericAlias
  3. from cpython.dict cimport PyDict_GetItem
  4. from cpython.object cimport PyObject
  5. cdef extern from "Python.h":
  6. # Call a callable Python object callable with exactly
  7. # 1 positional argument arg and no keyword arguments.
  8. # Return the result of the call on success, or raise
  9. # an exception and return NULL on failure.
  10. PyObject* PyObject_CallOneArg(
  11. object callable, object arg
  12. ) except NULL
  13. int PyDict_SetItem(
  14. object dict, object key, PyObject* value
  15. ) except -1
  16. void Py_DECREF(PyObject*)
  17. cdef class under_cached_property:
  18. """Use as a class method decorator. It operates almost exactly like
  19. the Python `@property` decorator, but it puts the result of the
  20. method it decorates into the instance dict after the first call,
  21. effectively replacing the function it decorates with an instance
  22. variable. It is, in Python parlance, a data descriptor.
  23. """
  24. cdef readonly object wrapped
  25. cdef object name
  26. def __init__(self, object wrapped):
  27. self.wrapped = wrapped
  28. self.name = wrapped.__name__
  29. @property
  30. def __doc__(self):
  31. return self.wrapped.__doc__
  32. def __get__(self, object inst, owner):
  33. if inst is None:
  34. return self
  35. cdef dict cache = inst._cache
  36. cdef PyObject* val = PyDict_GetItem(cache, self.name)
  37. if val == NULL:
  38. val = PyObject_CallOneArg(self.wrapped, inst)
  39. PyDict_SetItem(cache, self.name, val)
  40. Py_DECREF(val)
  41. return <object>val
  42. def __set__(self, inst, value):
  43. raise AttributeError("cached property is read-only")
  44. __class_getitem__ = classmethod(GenericAlias)
  45. cdef class cached_property:
  46. """Use as a class method decorator. It operates almost exactly like
  47. the Python `@property` decorator, but it puts the result of the
  48. method it decorates into the instance dict after the first call,
  49. effectively replacing the function it decorates with an instance
  50. variable. It is, in Python parlance, a data descriptor.
  51. """
  52. cdef readonly object func
  53. cdef object name
  54. def __init__(self, func):
  55. self.func = func
  56. self.name = None
  57. @property
  58. def __doc__(self):
  59. return self.func.__doc__
  60. def __set_name__(self, owner, object name):
  61. if self.name is None:
  62. self.name = name
  63. elif name != self.name:
  64. raise TypeError(
  65. "Cannot assign the same cached_property to two different names "
  66. f"({self.name!r} and {name!r})."
  67. )
  68. def __get__(self, inst, owner):
  69. if inst is None:
  70. return self
  71. if self.name is None:
  72. raise TypeError(
  73. "Cannot use cached_property instance"
  74. " without calling __set_name__ on it.")
  75. cdef dict cache = inst.__dict__
  76. cdef PyObject* val = PyDict_GetItem(cache, self.name)
  77. if val is NULL:
  78. val = PyObject_CallOneArg(self.func, inst)
  79. PyDict_SetItem(cache, self.name, val)
  80. Py_DECREF(val)
  81. return <object>val
  82. __class_getitem__ = classmethod(GenericAlias)