mirror of
https://github.com/python/cpython
synced 2024-11-05 18:12:54 +00:00
345cd37abe
The original tool wasn't working right and it was simpler to create a new one, partially re-using some of the old code. At this point the tool runs properly on the master. (Try: ./python Tools/c-analyzer/c-analyzer.py analyze.) It take ~40 seconds on my machine to analyze the full CPython code base. Note that we'll need to iron out some OS-specific stuff (e.g. preprocessor). We're okay though since this tool isn't used yet in our workflow. We will also need to verify the analysis results in detail before activating the check in CI, though I'm pretty sure it's close. https://bugs.python.org/issue36876
117 lines
3.6 KiB
Python
117 lines
3.6 KiB
Python
|
|
_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)
|