Welcome, guest | Sign In | My Account | Store | Cart

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

Python, 36 lines
 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:

  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.

1 comment

Charlie Clark 7 years, 8 months ago  # | flag

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?

Created by Raymond Hettinger on Mon, 24 Sep 2007 (PSF)
Python recipes (4591)
Raymond Hettinger's recipes (97)
HongxuChen's Fav (39)

Required Modules

  • (none specified)

Other Information and Tasks