Differences between revisions 1 and 7 (spanning 6 versions)
Revision 1 as of 2021-11-09 16:51:41
Size: 8829
Comment:
Revision 7 as of 2022-02-22 16:59:15
Size: 6784
Comment:
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
  `__slots__` has a mixed reputation in the Python community. On the one hand, they are considered to be popular. Lots of people like using them. Others say that they are badly understood, tricky to get right, and don't have much of an effect unless there are many instances of objects that use them. This article will explain what they are, how, and why to use them, and when not to use them. `__slots__` has a mixed reputation in the Python community. On the one hand, they are considered to be popular. Lots of people like using them. Others say that they are badly understood, tricky to get right, and don't have much of an effect unless there are many instances of objects that use them. This article will explain what they are, how, and why to use them, and when not to use them.
Line 5: Line 5:
  `__slots__` are discussed in the Python Language Reference under section 3.3.2, Customizing Attribute Access. The first thing we should understand is that `__slots__` is only used in the context of Python classes. `__slots__` is a class variable that is usually assigned a sequence of strings that are variable names used by instances. For example:
 {{{
    class Example():
        __slots__ = ('_slot_0', '_slot_1')
 }}}
 This declaration allows us to explicitly declare data members, causes Python to reserve space for them in memory, and prevents the creation of `__dict__ ` and `__weakref__` attributes. It also prevents the creation of any variables that aren't declared in `__slots__`.
`__slots__` are discussed in the Python Language Reference under section 3.3.2, Customizing Attribute Access. The first thing we should understand is that `__slots__` is only used in the context of Python classes. `__slots__` is a class variable that is usually assigned a sequence of strings that are variable names used by instances. For example:
{{{
class Example():
    __slots__ = ("slot_0", "slot_1")
         def __init__(self):
        self.slot_0 = "zero"
        self.slot_1 = "one"
        
x1 = Example()
print(x1.slot_0)
print(x1.slot_1)

    zero
    one
}}}
The `__slots__` declaration allows us to explicitly declare data members, causes Python to reserve space for them in memory, and prevents the creation of `__dict__ ` and `__weakref__` attributes. It also prevents the creation of any variables that aren't declared in `__slots__`.
Line 13: Line 24:
The short answer is slots are more efficient in terms of memory space and speed of access, and a bit safer than the default Python method of data access. By default, when Python creates a new instance of a class, it creates a `__dict__` attribute for the class. The `__dict__` attribute is a dictionary whose keys are the variable names and whose values are the variable values. For example, here is a simple class definition: The short answer is slots are more efficient in terms of memory space and speed of access, and a bit safer than the default Python method of data access. By default, when Python creates a new instance of a class, it creates a `__dict__` attribute for the class. The `__dict__` attribute is a dictionary whose keys are the variable names and whose values are the variable values. This allows for dynamic variable creation but can also lead to uncaught errors. For example, with the default `__dict__`, a misspelled variable name results in the creation of a new variable, but with `__slots__` it raises in an AttributeError.
Line 15: Line 26:
class Example(): class Example2():
Line 18: Line 29:
        self.var_0 = 'This is variable 0'
        self.var_1 = 'This is variable 1'
}}}
An interactive session looks like this:
{{{
>>> x = Example()
>>> print(x.__dict__.keys())
dict_keys(['var_0', 'var_1'])
        self.var_0 = "zero"
        
x2 = Example2()
x2.var0 = 0
Line 27: Line 34:
>>> print(x.__dict__.values())
dict_values(['This is variable 0', 'This is variable 1'])
}}}
We can now assign values to the variables with dot notation.
{{{
>>> x.var_0 = 'zero'
>>> x.var1 = 'one'
>>> print(x.__dict__.keys())
dict_keys(['var_0', 'var_1', 'var1'])
print(x2.__dict__.keys())
print(x2.__dict__.values())
```
Line 37: Line 38:
>>> print(x.__dict__.values())
dict_values(['zero', 'This is variable 1', 'one'])
}}}
The output here is not what was expected. The underscore was left out of the var_1 assignment and as a result we now have an extra variable in our dictionary named 'var1'. With `__slots__`, a misspelled variable name results in an Attribute Error. `__slots__` uses less memory space than `__dict__`, and direct memory access is faster than dictionary lookups. These are some of the reasons why you might want to use slots.
    dict_keys(['var_0', 'var0'])
    dict_values(['zero', 0])
Line 43: Line 42:
== Slots Basics ==
In the basic case, slots are easy to use. For example, here is a simple class definition:
x1.slot1 = 1

    ---------------------------------------------------------------------------

    AttributeError Traceback (most recent call last)

    Input In [3], in <module>
    ----> 1 x1.slot1 = 1


    AttributeError: 'Example' object has no attribute 'slot1'
}}}

As mentioned earlier, a `__slots__` declaration uses less memory than a dictionary, and direct memory access is faster than dictionary lookups. `__slots__` variables use dot notation for assignment and referencing in exactly the same way as the default method. But it should be noted that attempting to access the `__slots__` tuple by subscription returns only the name of the variable.

Line 46: Line 59:
class Example(): x1.__slots__[0]

    'slot_0'
}}}

== Using Other Types for `__slots__` ==
The Python documentation states that any non-string iterable can be used for the `__slots__` declaration. For example, it is possible to use a list, and a user might want to take advantage of the fact that a list is mutable, whereas a tuple is not. It is possible to do so, but keep in mind two key points:

 1. `__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.
 1. You cannot access the memory allocated by the `__slots__` declaration by using subscription. You will get only what is currently stored in the list.

It is usually best to stick to the base case in order to avoid confusion and unexpected results. The following example shows how things can get confusing if you mutate a `__slots__` list.
{{{
class Example3():
    __slots__ = ["slot_0"]
Line 48: Line 75:
    __slots__ = ('_slot_0', '_slot_1')     def set_0(self, _value):
        self.__slots__[0] = _value
        
    def get_0(self):
        return self.__slots__[0]
Line 50: Line 81:
    def __init__(self):
        self._slot_0 = 'This is slot 0'
        self._slot_1 = 'This is slot 1'
a = Example3()
b = Example3()

a.set_0("zero")
b.set_0("not zero")
a.slot_0 = 0

print(a.get_0())
print(a.slot_0)
   
```
    not zero
    0
Line 54: Line 95:
An interpreter session looks like this:
{{{
>>> x = Example()
>>> print(x._slot_0)
This is slot 0

>>> print(x._slot_1)
This is slot 1

>>> x._slot_0 = 'Is _slot_1 here?'
>>> x._slot_1 = "_slot_1 is here."
>>> print(x._slot_0, "\n", x._slot_1)
Is _slot_1 here?
 _slot_1 is here.

}}}
That's it. To the user who is writing code, slots look and behave exactly like the default. Once we've declared and initialized our variables, we can reference and assign to them using dot notation.

== Using A List For Slots ==
The previous example used a tuple for the `__slots__` declaration. The Python documentation states that any non-string iterable can be used. This section describes how slots behavior can change when using a list for the declaration.

For the most part, using a list for the `__slots__` declaration looks and works exactly like using a tuple.
{{{
class Example():
    
    __slots__ = ['_slot_0', '_slot_1']
    
    def __init__(self):
        self._slot_0 = 'This is slot 0'
        self._slot_1 = 'This is slot 1'
}}}
Using the same code as the previous interpreter session will produce exactly the same results. But unlike a tuple, a list is mutable, and there may be cases when we want to use the list. For example, we can implement get and set functions that access the list, as shown below.
{{{
    def _set(self, _slot, _value):
        self.__slots__[_slot] = _value
        return None
    
    def _get(self, _slot):
        return self.__slots__[_slot]
   
}}}
Attempting to use both dot notation and getters and setters will produce results that may be surprising. Using the two previous examples,
{{{
>>> x = Example()
>>> print(x._slot_0)
This is slot 0

>>> print(x._get(0))
_slot_0

>>> x._set(0, 'zero')
>>> print(x._slot_0)
This is slot 0

>>> print(x._get(0))
zero
}}}
This session shows that accessing `x._slot_0` works as it did before, but attempting to access `_slot_0` with the get function returns only the name of the variable. If we then use the set function to insert a value, we see that the value returned by `x._slot_0` hasn't changed, but the get function retrieves the value inserted by the set function.

Python does not allow access through the list to data that has been set through `__slots__` variables, and we cannot overwrite `__slots__` variables by writing to the list directly. But we can still use the list like any other list without altering the variable names or the data they contain. This behavior can be useful in some cases. For example, when collecting data to be written to a file, you may want to assign values using the variable names. When reading the same data from a file, it may be more efficient to use the list, allowing you to return the entire data structure.
Line 118: Line 99:
"`__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."  "`__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."
Line 120: Line 101:
It is not necessary to explicitly implement descriptors in order to use slots. The point here is that default values cannot be set using class attributes.

{{{
class Example():
    
    __slots__ = ['_slot_0', '_slot_1']
    
    _slot_0 = 'This is slot 0'
    _slot_1 = 'This is slot 1'

Out: Value Error: '_slot_0' in __slots__ conflicts with class variable
}}}
Likewise,
{{{
class Example():
    
    __slots__ = ['_slot_0', '_slot_1']
    
    __slots__[0] = 'This is slot 0'
    __slots__[1] = 'This is slot 1'

Out: Type Error: __slots__ must be identifiers
}}}
If desired, default values for `__slots__` variables may be set in the class `__init__()` method, as shown in previous examples, but it is not necessary to do so. Values may be assigned to slots variables that have not been initialized with default values.
{{{
class Example():
    
    __slots__ = ['_slot_0', '_slot_1']
    
    def __init__(self):
        return None

>>> x = Example()
>>> x._slot_0 = 'zero'
>>> print(x._slot_0)
Out: zero
}}}
Some readers might find this documentation confusing. It is not necessary for a user to implement descriptors in order to use `__slots__`. The point to remember is that default values for variables declared in `__slots__` cannot be set using class attributes. If default values are desired, they must be set in the `__init__(self)` definition. However, it is not necessary to assign all variables a value in the `__init__` function. As long as it has been declared in `__slots__`, a variable can be assigned a value using dot notation after the class has been instantiated.
Line 165: Line 110:
There are a few things to be aware of when going beyond the basics. Slots variables declared in parents are available in child classes. However, child subclasses will get a `__dict__` and `__weakref__` unless they also define `__slots__`, which should only contain names of additional slots. Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots. The other bases must have empty slot layouts. For additional details, please see the Python Language Reference, section 3.3.2.4.1. There are a few things to be aware of when going beyond the basics. Slots variables declared in parents are available in child classes. However, child subclasses will have `__dict__` and `__weakref__` attributes unless they also define `__slots__`, which should only contain names of additional slots. Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots. The other bases must have empty slot layouts. For additional details, please see the Python Language Reference, section 3.3.2.4.1.
Line 168: Line 113:
Slots are a simple, easy to use, efficient, and safe alternative to Python's default method of data access. The only known exception is when another object requires access to the `__dict__` attribute. Using `__slots__` is straightforward. They are a simple, efficient, and safe alternative to Python's default method of data access. The only known exception is when another object requires access to the `__dict__` attribute.

Introduction

__slots__ has a mixed reputation in the Python community. On the one hand, they are considered to be popular. Lots of people like using them. Others say that they are badly understood, tricky to get right, and don't have much of an effect unless there are many instances of objects that use them. This article will explain what they are, how, and why to use them, and when not to use them.

What Is `__slots__` ?

__slots__ are discussed in the Python Language Reference under section 3.3.2, Customizing Attribute Access. The first thing we should understand is that __slots__ is only used in the context of Python classes. __slots__ is a class variable that is usually assigned a sequence of strings that are variable names used by instances. For example:

class Example():
    __slots__ = ("slot_0", "slot_1")
    
    def __init__(self):
        self.slot_0 = "zero"
        self.slot_1 = "one"
        
x1 = Example()
print(x1.slot_0)
print(x1.slot_1)

    zero
    one

The __slots__ declaration allows us to explicitly declare data members, causes Python to reserve space for them in memory, and prevents the creation of __dict__  and __weakref__ attributes. It also prevents the creation of any variables that aren't declared in __slots__.

Why Use `__slots__`?

The short answer is slots are more efficient in terms of memory space and speed of access, and a bit safer than the default Python method of data access. By default, when Python creates a new instance of a class, it creates a __dict__ attribute for the class. The __dict__ attribute is a dictionary whose keys are the variable names and whose values are the variable values. This allows for dynamic variable creation but can also lead to uncaught errors. For example, with the default __dict__, a misspelled variable name results in the creation of a new variable, but with __slots__ it raises in an AttributeError.

class Example2():
    
    def __init__(self):
        self.var_0 = "zero"
        
x2 = Example2()
x2.var0 = 0

print(x2.__dict__.keys())
print(x2.__dict__.values())
```

    dict_keys(['var_0', 'var0'])
    dict_values(['zero', 0])


x1.slot1 = 1

    ---------------------------------------------------------------------------

    AttributeError                            Traceback (most recent call last)

    Input In [3], in <module>
    ----> 1 x1.slot1 = 1


    AttributeError: 'Example' object has no attribute 'slot1'

As mentioned earlier, a __slots__ declaration uses less memory than a dictionary, and direct memory access is faster than dictionary lookups. __slots__ variables use dot notation for assignment and referencing in exactly the same way as the default method. But it should be noted that attempting to access the __slots__ tuple by subscription returns only the name of the variable.

x1.__slots__[0]

    'slot_0'

Using Other Types for `__slots__`

The Python documentation states that any non-string iterable can be used for the __slots__ declaration. For example, it is possible to use a list, and a user might want to take advantage of the fact that a list is mutable, whereas a tuple is not. It is possible to do so, but keep in mind two key points:

  1. __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.

  2. You cannot access the memory allocated by the __slots__ declaration by using subscription. You will get only what is currently stored in the list.

It is usually best to stick to the base case in order to avoid confusion and unexpected results. The following example shows how things can get confusing if you mutate a __slots__ list.

class Example3():
    __slots__ = ["slot_0"]
    
    def set_0(self, _value):
        self.__slots__[0] = _value
        
    def get_0(self):
        return self.__slots__[0]
    
a = Example3()
b = Example3()

a.set_0("zero")
b.set_0("not zero")
a.slot_0 = 0

print(a.get_0())
print(a.slot_0)
   
```
    not zero
    0

Default Values

The following text appears in the Python Language Reference section 3.3.2.4.1:

  • "__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."

Some readers might find this documentation confusing. It is not necessary for a user to implement descriptors in order to use __slots__. The point to remember is that default values for variables declared in __slots__ cannot be set using class attributes. If default values are desired, they must be set in the __init__(self) definition. However, it is not necessary to assign all variables a value in the __init__ function. As long as it has been declared in __slots__, a variable can be assigned a value using dot notation after the class has been instantiated.

Why Not Use Slots?

There may be cases when you might not want to use __slots__; for example, if you would like for your class to use dynamic attribute creation or weak references. In those cases, you can add '__dict__' or '__weakref__' as the last element in the __slots__ declaration.

Certain Python objects may depend on the __dict__ attribute. For example, descriptor classes depend on the __dict__ attribute being present in the owner class. Programmers may want to avoid __slots__ in any case where another Python object requires __dict__ or __weak_ref__to be present. According to the Descriptor How To Guide for Python 3.9, the functools.cached_property() is another example that requires an instance dictionary to function correctly.

Beyond The Basics

There are a few things to be aware of when going beyond the basics. Slots variables declared in parents are available in child classes. However, child subclasses will have __dict__ and __weakref__ attributes unless they also define __slots__, which should only contain names of additional slots. Multiple inheritance with multiple slotted parent classes can be used, but only one parent is allowed to have attributes created by slots. The other bases must have empty slot layouts. For additional details, please see the Python Language Reference, section 3.3.2.4.1.

Conclusion

Using __slots__ is straightforward. They are a simple, efficient, and safe alternative to Python's default method of data access. The only known exception is when another object requires access to the __dict__ attribute.

UsingSlots (last edited 2022-02-22 16:59:15 by ChristopherCardea)

Unable to edit the page? See the FrontPage for instructions.