Optimizing your code with __slots__

Python like many other dynamic programming languages can be extended. Objects and definitions can be modified at runtime. This gives the developer great power but this power comes at a price.

During the execution two instances of the same object can have different properties. This is why python accesses these properties through a dictionary __dict__. This flexibility comes at the expense of CPU and memory. Most of the time this is not a big deal, but it can become one if you have tens of thousand of instances of an object with many attributes and long attribute names.

Here is how it works.

class Collection(object):
  def __init__(self):
    self._collection_data = set([])
  def add(self, elem):
    self._collection_data.add(elem)
  def __repr__(self):
    return ', '.join(self._collection_data)

In [2]: c = Collection()
In [3]: c.add('this is my data')
In [4]: c
Out[4]: this is my data

In [5]: c.__dict__
Out[5]: {'_collection_data': set(['this is my data'])}

You can now create a new property. This new property will be added into __dict__

In [6]: c.new_property = 'hello property'
In [7]: c.__dict__
Out[7]: {'_collection_data': set(['this is my data']),
         new_property': 'hello property'}
In [8]: c.new_property
Out[8]: hello property

By setting the attribute __slots__ with the list of the properties your object will handle, python will no longer use __dict__ to dynamically manage the object properties. Your object instances will no longer be dynamic. Meaning you won't be able to set other properties other than the ones that have already be defined in __slots__.

You can use __slots__ when you know in advance what will be the properties of your class and you know that no other property will be added at runtime. __slots__ renders your class static in the same way a struct in C or C++ would.

The __slots__ attribute tells python to not create a __dict__ for every instance of the object. Creating a new property will raise an AttributeError exception.

class Collection(object):
  __slots__ = ['_collection_data']
  def __init__(self):
    self._collection_data = set([])
  def add(self, elem):
    self._collection_data.add(elem)
  def __repr__(self):
    return ', '.join(self._collection_data)

In [2]: c = Collection()
In [3]: c.add(10)
In [4]: c.new_property = 12

AttributeError: 'Collection' object has no attribute 'new_property'

In [6]: dir(c)
Out[6]:
['__class__',
 '__delattr__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasshook__',
 '_collection_data',
 'add']

When you use __slots__, the instance created for that class has no __dict__ attribute. Instead, all properties access is done directly.

Slots change the behavior of a Python class and should only be considered if you are creating thousands of instances of an object.

They can be abused by control freaks and static typing lovers. Python is and should remain dynamic. Control freaks should consider abusing metaclasses and static typing lovers should play with decorators.


Comments !