ActiveState Code

Recipe 532903: How __slots__ are implemented


Pure python equivalent of the __slots__ implementation using descriptors and a metaclass.

Python
 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

Discussion

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:

  1. 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.

  2. Whenever __slots__ are present, the __new__ method prevents an instance dictionary from being created. That makes __slots__ useful for creating very lightweight instances.

Sign in to comment