Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
allendowney
GitHub Repository: allendowney/cpython
Path: blob/main/Tools/c-analyzer/c_common/clsutil.py
12 views
1
2
_NOT_SET = object()
3
4
5
class Slot:
6
"""A descriptor that provides a slot.
7
8
This is useful for types that can't have slots via __slots__,
9
e.g. tuple subclasses.
10
"""
11
12
__slots__ = ('initial', 'default', 'readonly', 'instances', 'name')
13
14
def __init__(self, initial=_NOT_SET, *,
15
default=_NOT_SET,
16
readonly=False,
17
):
18
self.initial = initial
19
self.default = default
20
self.readonly = readonly
21
22
# The instance cache is not inherently tied to the normal
23
# lifetime of the instances. So must do something in order to
24
# avoid keeping the instances alive by holding a reference here.
25
# Ideally we would use weakref.WeakValueDictionary to do this.
26
# However, most builtin types do not support weakrefs. So
27
# instead we monkey-patch __del__ on the attached class to clear
28
# the instance.
29
self.instances = {}
30
self.name = None
31
32
def __set_name__(self, cls, name):
33
if self.name is not None:
34
raise TypeError('already used')
35
self.name = name
36
try:
37
slotnames = cls.__slot_names__
38
except AttributeError:
39
slotnames = cls.__slot_names__ = []
40
slotnames.append(name)
41
self._ensure___del__(cls, slotnames)
42
43
def __get__(self, obj, cls):
44
if obj is None: # called on the class
45
return self
46
try:
47
value = self.instances[id(obj)]
48
except KeyError:
49
if self.initial is _NOT_SET:
50
value = self.default
51
else:
52
value = self.initial
53
self.instances[id(obj)] = value
54
if value is _NOT_SET:
55
raise AttributeError(self.name)
56
# XXX Optionally make a copy?
57
return value
58
59
def __set__(self, obj, value):
60
if self.readonly:
61
raise AttributeError(f'{self.name} is readonly')
62
# XXX Optionally coerce?
63
self.instances[id(obj)] = value
64
65
def __delete__(self, obj):
66
if self.readonly:
67
raise AttributeError(f'{self.name} is readonly')
68
self.instances[id(obj)] = self.default # XXX refleak?
69
70
def _ensure___del__(self, cls, slotnames): # See the comment in __init__().
71
try:
72
old___del__ = cls.__del__
73
except AttributeError:
74
old___del__ = (lambda s: None)
75
else:
76
if getattr(old___del__, '_slotted', False):
77
return
78
79
def __del__(_self):
80
for name in slotnames:
81
delattr(_self, name)
82
old___del__(_self)
83
__del__._slotted = True
84
cls.__del__ = __del__
85
86
def set(self, obj, value):
87
"""Update the cached value for an object.
88
89
This works even if the descriptor is read-only. This is
90
particularly useful when initializing the object (e.g. in
91
its __new__ or __init__).
92
"""
93
self.instances[id(obj)] = value
94
95
96
class classonly:
97
"""A non-data descriptor that makes a value only visible on the class.
98
99
This is like the "classmethod" builtin, but does not show up on
100
instances of the class. It may be used as a decorator.
101
"""
102
103
def __init__(self, value):
104
self.value = value
105
self.getter = classmethod(value).__get__
106
self.name = None
107
108
def __set_name__(self, cls, name):
109
if self.name is not None:
110
raise TypeError('already used')
111
self.name = name
112
113
def __get__(self, obj, cls):
114
if obj is not None:
115
raise AttributeError(self.name)
116
# called on the class
117
return self.getter(None, cls)
118
119