Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does __slots__ = ('__dict__',) produce smaller instances?

class Spam(object):
    __slots__ = ('__dict__',)

Produces instances smaller than those of a "normal" class. Why is this?

Source: David Beazley's recent tweet.

like image 303
Markus Meskanen Avatar asked Oct 12 '16 15:10

Markus Meskanen


People also ask

What is __ slots __ and when is it useful?

__slots__ is a class variable. If you have more than one instance of your class, any change made to __slots__ will show up in every instance. You cannot access the memory allocated by the __slots__ declaration by using subscription. You will get only what is currently stored in the list.

How do Python slots work?

Slots in Python is a special mechanism that is used to reduce memory of the objects. In Python, all the objects use a dynamic dictionary for adding an attribute. Slots is a static type method in this no dynamic dictionary are required for allocating attribute.

What is __ dict __ in Python?

All objects in Python have an attribute __dict__, which is a dictionary object containing all attributes defined for that object itself. The mapping of attributes with its values is done to generate a dictionary.


1 Answers

To me, it looks like the memory savings come from the lack of a __weakref__ on the instance.

So if we have:

class Spam1(object):
    __slots__ = ('__dict__',)

class Spam2(object):
    __slots__ = ('__dict__', '__weakref__')

class Spam3(object):
    __slots__ = ('foo',)

class Eggs(object):
    pass

objs = Spam1(), Spam2(), Spam3(), Eggs()
for obj in objs:
    obj.foo = 'bar'

import sys
for obj in objs:
    print(type(obj).__name__, sys.getsizeof(obj))

The results (on python 3.5.2) are:

Spam1 48
Spam2 56
Spam3 48
Eggs 56

We see that Spam2 (which has a __weakref__) is the same size as Eggs (a traditional class).

Note that normally, this savings is going to be completely insignificant (and prevents you from using weak-references in your slots enabled classes). Generally, savings from __slots__ come from the fact that they don't create a __dict__ in the first place. Since __dict__ are implemented using a somewhat sparse table (in order to help avoid hash collisions and maintain O(1) lookup/insert/delete), there's a fair amount of space that isn't used for each dictionary that your program creates. If you add '__dict__' to your __slots__ though, you miss out on this optimization (a dict is still created).

To explore this a little more, we can add more slots:

class Spam3(object):
    __slots__ = ('foo', 'bar')

Now if we re-run, we see that it takes:

Spam1 48
Spam2 56
Spam3 56
Eggs 56

So each slot takes 8 bytes on the instance (for me -- likely because 8 bytes is sizeof(pointer) on my system). Also note that __slots__ is implemented by making descriptors (which live on the class, not the instance). So, the instance (even though you might find __slots__ listed via dir(instance)) isn't actually carrying around a __slots__ value) -- That's being carried around by the class.

This also has the consequence that your slots enabled class can't set "default" values... e.g. the following code doesn't work:

class Foo(object):
    __slots__ = ('foo',)
    foo = 'bar'

So to boil it down:

  • each "slot" on an instance takes up the size of a pointer on your system.
  • without __slots__ = ('__dict__',) a __dict__ slot and a __weakref__ slot is created on the instance
  • with __slots__ = ('__dict__',), a __dict__ slot is created but a __weakref__ slot is not create on the instance.
  • In neither case is __slots__ actually put on the instance. It lives on the class (even though you might see it from dir(instance)).
  • The savings you reap from using __slots__ in this way is likely to be insignificant. Real savings from __slots__ happen when you do not create a dict for the instance (since dict take up a more storage than the sum of the storage required for their contents due to somewhat sparse packing data in the data-structure). On top of that, there are downsides to using slots this way (e.g. no weak-references to your instances).
like image 162
mgilson Avatar answered Sep 22 '22 15:09

mgilson