Each python object has a _dict_ attribute which is a dictionary containing
all other attributes. E.g. when you type self.attr python is actually doing
self.dict[‘attr’].
As you can imagine using a dictionary to store attribute takes some extra space
& time for accessing it.
What if you already knew which attributes your class is going to have . Then
using _dict_ to store attributes doesn’t seem to be a good idea as we don’t
need the dynamic nature of dicts .
This is where slots come to rescue .
What are slots in python
_slots_ is a class variable which allows us declare the attributes of a
class explicitly and denying the creation of _dict_ and
_weakref_ for object instances .
The expected attributes can be assigned to _slots_ as a string, iterable,
or sequence of strings with variable names used by instance .
Space saved by not having a _dict_ can be significant . Slots also
improve the attribute lookup speed .
Usage of slots
Python utilises a considerably more compact internal representation for
instances when you define _slots_. Instead of a dictionary, each instance
is created around a small fixed-sized array, similar to a tuple or list.
As a result of using slots, you can no longer add new attributes to
instances;
you are limited to the attribute names given in the _slots_ specifier.( You
can get around this by adding _dict_ to the _slot_ . The dict will only
initialized when a dynamic attribute is added ) .
Example showing usage of slots :
# A email class with out using slots
class Email :
def __init__(self,subject,to,message) :
self.subject = subject
self.message = message
self.to = to
class EmailWithSlots :
__slots__ = ('subject','to','message')
def __init__(self,subject,to,message) :
self.subject = subject
self.message = message
self.to = to
email = EmailWithSlots('test','me@gmail.com','testing slots')
email.subject
# >> test
email.__dict__ # cant access __dict__ because its not created
# AttributeError Traceback (most recent call last)
# <ipython-input-40-b95667a7fb92> in <module>
# ----> 1 email.__dict__
#
#AttributeError: 'EmailWithSlots' object has no attribute '__dict__'
email.from = "aabid@gmail.com" # cant add an atribute that not in __slots__
# ---------------------------------------------------------------------------
# AttributeError Traceback (most recent call last)
# <ipython-input-42-87651e4df821> in <module>
# ----> email.from = "aabid@gmail.com"
#
#AttributeError: 'EmailWithSlots' object has no attribute 'from'
Pros And Cons of slots
Pros
- using _slots_ has faster attribute access .
- _slots_ reduces memory usage
Cons
- Fixed attributes ( You can get around this by adding _dict_ to the _slots_ . The dict will only initialized when a dynamic attribute is added )
- _slots_ are implemented at the class level by creating descriptors for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by _slots_; otherwise, the class attribute would overwrite the descriptor assignment.
Inheritance with slots .
Working with slots and Inheritance is a little tricky and need to be taken care
of . Here is How :
- The _slots_ defined on parent class are automatically accessible to the child .
class BaseClass :
__slots__ = ['x','y','z']
class Inherited(BaseClass) :
pass
Inherited.__slots__
#>> ['x', 'y', 'z']
- If the subclass doesn’t specify slots ( empty or new ) . The subclass will get _dict_ and _weakref_ attributes in addition to slots from parent
class BaseClass :
__slots__ = ['x','y','z']
class Inherited(BaseClass) :
pass
class InheritedWithSlots(BaseClass) :
__slots__ = ()
Inherited.__slots__
#>> ['x', 'y', 'z']
Inherited.__dict__
#>> {}
InheritedWithSlots().__dict__
# AttributeError
- Nonempty _slots_ does not work for classes derived from “variable-length” built-in types such as int, bytes and tuple.
# this works fine because we are not using any additional slots in subclass class MyInt(int): __slots__ = () # This will panic because we are adding non-empty slots . class NewInt(int) : __slots__ = (x,y) #TypeError: nonempty __slots__ not supported for subtype of 'int'
- You can also use multiple inheritance with slots . But only one parent can
have non empty _slots_. Otherwise a typeerror is raised .#lets have three slotted base classes class foo: __slots__ = ("x","y") class bar : __slots__ = ("a","b") class baz : __slots__=() # this will raise TypeError as we are inheriting from two classes # with nonempty slots class foobar(foo,bar) : pass #>>TypeError: multiple bases have instance lay-out conflict # This shall work as only one of the inherited class has nonempty slots class FooBaz(foo,bar) : pass
Conclusion :
_slots_ allow us to explicitly state which instance variables to expect in
the object
Which gives us following benefits
- Faster Attribute Access
- Saves Memory
A typical use case
For classes that primarily serve as simple data structures, you can often greatly reduce
the memory footprint of instances by adding the _slots_ attribute to the
class definition.
When slots is a bad idea .
- Avoid them when you want to perform _class_ assignment with another class that doesn’t have them (and you can’t add them) unless the slot layouts are identical. (I am very interested in learning who is doing this and why.)
- Avoid them if you want to subclass variable length builtins like long, tuple, or str, and you want to add attributes to them.
- Avoid them if you insist on providing default values via class attributes for instance variables.
Thanks , for reading till end Happy coding ..