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. 

 Download
Download Copy to clipboard
Copy to clipboard
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?