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