summaryrefslogtreecommitdiffstats
path: root/Tools/c-analyzer/c_common/clsutil.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/c-analyzer/c_common/clsutil.py')
-rw-r--r--Tools/c-analyzer/c_common/clsutil.py117
1 files changed, 117 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_common/clsutil.py b/Tools/c-analyzer/c_common/clsutil.py
new file mode 100644
index 0000000..aa5f6b9
--- /dev/null
+++ b/Tools/c-analyzer/c_common/clsutil.py
@@ -0,0 +1,117 @@
+
+_NOT_SET = object()
+
+
+class Slot:
+ """A descriptor that provides a slot.
+
+ This is useful for types that can't have slots via __slots__,
+ e.g. tuple subclasses.
+ """
+
+ __slots__ = ('initial', 'default', 'readonly', 'instances', 'name')
+
+ def __init__(self, initial=_NOT_SET, *,
+ default=_NOT_SET,
+ readonly=False,
+ ):
+ self.initial = initial
+ self.default = default
+ self.readonly = readonly
+
+ # The instance cache is not inherently tied to the normal
+ # lifetime of the instances. So must do something in order to
+ # avoid keeping the instances alive by holding a reference here.
+ # Ideally we would use weakref.WeakValueDictionary to do this.
+ # However, most builtin types do not support weakrefs. So
+ # instead we monkey-patch __del__ on the attached class to clear
+ # the instance.
+ self.instances = {}
+ self.name = None
+
+ def __set_name__(self, cls, name):
+ if self.name is not None:
+ raise TypeError('already used')
+ self.name = name
+ try:
+ slotnames = cls.__slot_names__
+ except AttributeError:
+ slotnames = cls.__slot_names__ = []
+ slotnames.append(name)
+ self._ensure___del__(cls, slotnames)
+
+ def __get__(self, obj, cls):
+ if obj is None: # called on the class
+ return self
+ try:
+ value = self.instances[id(obj)]
+ except KeyError:
+ if self.initial is _NOT_SET:
+ value = self.default
+ else:
+ value = self.initial
+ self.instances[id(obj)] = value
+ if value is _NOT_SET:
+ raise AttributeError(self.name)
+ # XXX Optionally make a copy?
+ return value
+
+ def __set__(self, obj, value):
+ if self.readonly:
+ raise AttributeError(f'{self.name} is readonly')
+ # XXX Optionally coerce?
+ self.instances[id(obj)] = value
+
+ def __delete__(self, obj):
+ if self.readonly:
+ raise AttributeError(f'{self.name} is readonly')
+ self.instances[id(obj)] = self.default # XXX refleak?
+
+ def _ensure___del__(self, cls, slotnames): # See the comment in __init__().
+ try:
+ old___del__ = cls.__del__
+ except AttributeError:
+ old___del__ = (lambda s: None)
+ else:
+ if getattr(old___del__, '_slotted', False):
+ return
+
+ def __del__(_self):
+ for name in slotnames:
+ delattr(_self, name)
+ old___del__(_self)
+ __del__._slotted = True
+ cls.__del__ = __del__
+
+ def set(self, obj, value):
+ """Update the cached value for an object.
+
+ This works even if the descriptor is read-only. This is
+ particularly useful when initializing the object (e.g. in
+ its __new__ or __init__).
+ """
+ self.instances[id(obj)] = value
+
+
+class classonly:
+ """A non-data descriptor that makes a value only visible on the class.
+
+ This is like the "classmethod" builtin, but does not show up on
+ instances of the class. It may be used as a decorator.
+ """
+
+ def __init__(self, value):
+ self.value = value
+ self.getter = classmethod(value).__get__
+ self.name = None
+
+ def __set_name__(self, cls, name):
+ if self.name is not None:
+ raise TypeError('already used')
+ self.name = name
+
+ def __get__(self, obj, cls):
+ if obj is not None:
+ raise AttributeError(self.name)
+ # called on the class
+ return self.getter(None, cls)