Pure python equivalent of the __slots__ implementation using descriptors and a metaclass.
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 | 'Rough approximation of how slots work'
class Member(object):
'Descriptor implementing slot lookup'
def __init__(self, i):
self.i = i
def __get__(self, obj, type=None):
return obj._slotvalues[self.i]
def __set__(self, obj, value):
obj._slotvalues[self.i] = value
class Type(type):
'Metaclass that detects and implements _slots_'
def __new__(self, name, bases, namespace):
slots = namespace.get('_slots_')
if slots:
for i, slot in enumerate(slots):
namespace[slot] = Member(i)
original_init = namespace.get('__init__')
def __init__(self, *args, **kwds):
'Create _slotvalues list and call the original __init__'
self._slotvalues = [None] * len(slots)
if original_init is not None:
original_init(self, *args, **kwds)
namespace['__init__'] = __init__
return type.__new__(self, name, bases, namespace)
class Object(object):
__metaclass__ = Type
class A(Object):
_slots_ = 'x', 'y'
a = A()
a.x = 10
print a.x, a.y
|
In CPython, when class A defines __slots__=('x','y') then A.x is a "member_descriptor" with __get__ and __set__ methods which directly access memory allocated to each instance.
As an illustration, the above code shows a rough equivalent using pure Python. In the example, when the metaclass sees that _slots_ have been defined for 'x' and 'y', it creates two additional class variables, x=Member(0) and y=Member(1). Then, it wraps the __init__() method so that new instances get created with an initialized _slotvalues list.
To make it obvious that the illustrative code is running, _slots_ is spelled with single underscores (to differentiate it from the CPython version which is spelled with double underscores).
The CPython version differs in that:
Instead of a _slotvalues pointer to an external list, CPython allocates memory directly inside each instance. Accordingly, the member descriptor accesses that memory directly instead of using a list lookup.
Whenever __slots__ are present, the __new__ method prevents an instance dictionary from being created. That makes __slots__ useful for creating very lightweight instances.
What's the best way to combine __slots__ with other descriptors (where the memory reduction given by slots is desired as well as management of values by descriptors) to avoid name conflicts?