class Spam(object):
__slots__ = ('__dict__',)
Produces instances smaller than those of a "normal" class. Why is this?
Source: David Beazley's recent tweet.
__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.
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.
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.
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:
__slots__ = ('__dict__',)
a __dict__
slot and a __weakref__
slot is created on the instance__slots__ = ('__dict__',)
, a __dict__
slot is created but a __weakref__
slot is not create on the instance.__slots__
actually put on the instance. It lives on the class (even though you might see it from dir(instance)
).__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).If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With