Viewing file: clsutil.py (3.6 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
_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)
|