
Utilisation de __slots__ pour optimiser la mémoire
Découvrez comment utiliser l'attribut de classe spécial '__slots__' en Python pour réduire la consommation mémoire de vos objets. Comprenez les avantages, les limitations et les compromis de cette technique d'optimisation.
Comment Python stocke les attributs d'instance : le dictionnaire __dict__
Par défaut, Python stocke les attributs d'instance d'un objet dans un dictionnaire spécial appelé `__dict__`. Ce dictionnaire associe les noms des attributs (sous forme de chaînes de caractères) à leurs valeurs.
Exemple :
class MaClasse:
def __init__(self, x, y):
self.x = x
self.y = y
obj = MaClasse(10, 20)
print(obj.__dict__) # Affiche {'x': 10, 'y': 20}L'utilisation d'un dictionnaire pour stocker les attributs offre une grande flexibilité : vous pouvez ajouter, supprimer ou modifier des attributs dynamiquement à tout moment.
Cependant, cette flexibilité a un coût en termes de mémoire. Les dictionnaires sont des structures de données relativement coûteuses en mémoire, et chaque objet a son propre dictionnaire `__dict__`.
Si vous créez un *très grand nombre* d'objets d'une même classe, et que ces objets ont un nombre *limité* et *connu à l'avance* d'attributs, l'utilisation de dictionnaires pour stocker les attributs peut devenir un goulot d'étranglement en termes de consommation mémoire.
__slots__ : une alternative pour économiser de la mémoire
Python offre un mécanisme pour optimiser l'utilisation de la mémoire dans ce cas précis : l'attribut de classe spécial `__slots__`.
En définissant `__slots__` dans une classe, vous pouvez spécifier *explicitement* quels attributs d'instance les objets de cette classe auront. Au lieu d'utiliser un dictionnaire (`__dict__`) pour stocker les attributs, Python utilisera une structure de données plus compacte (un tableau).
Syntaxe :
class MaClasse:
__slots__ = ('attribut1', 'attribut2', 'attribut3')
def __init__(self, val1, val2, val3):
self.attribut1 = val1
self.attribut2 = val2
self.attribut3 = val3- `__slots__` est une variable de classe qui doit être une séquence (généralement un tuple ou une liste) de chaînes de caractères. Chaque chaîne est le nom d'un attribut d'instance autorisé.
- Lorsque `__slots__` est défini, les objets de la classe *n'auront plus* de dictionnaire `__dict__` (sauf si vous l'incluez explicitement dans `__slots__`, ce qui est généralement déconseillé).
- Vous ne pourrez plus ajouter dynamiquement de nouveaux attributs aux objets de la classe après leur création. Seuls les attributs listés dans `__slots__` seront autorisés.
Exemple :
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(2, 3)
print(p.x, p.y)
# p.z = 5 # Lèverait une AttributeError : 'Point' object has no attribute 'z'
# print(p.__dict__) # Lèverait une AttributeError : 'Point' object has no attribute '__dict__'Dans cet exemple, les objets de la classe `Point` n'auront que les attributs `x` et `y`. Ils n'auront pas de dictionnaire `__dict__`. Toute tentative d'ajouter un nouvel attribut (comme `p.z = 5`) lèvera une `AttributeError`.
Avantages et inconvénients de __slots__
`__slots__` offre des avantages, mais aussi des inconvénients :
Avantages :
- Réduction de la consommation mémoire : L'utilisation de `__slots__` peut réduire considérablement la consommation mémoire si vous créez un grand nombre d'objets (des milliers, des millions, ...). Le gain en mémoire peut être significatif (de l'ordre de 30% à 50%, voire plus, selon les cas).
- Accès aux attributs légèrement plus rapide : L'accès aux attributs peut être légèrement plus rapide avec `__slots__`, car Python n'a pas besoin de faire une recherche dans un dictionnaire.
Inconvénients :
- Perte de flexibilité : Vous ne pouvez plus ajouter dynamiquement de nouveaux attributs aux objets après leur création.
- Héritage plus complexe : L'utilisation de `__slots__` dans les hiérarchies de classes peut être délicate (voir section suivante).
- Incompatible avec certaines fonctionnalités : Certaines fonctionnalités de Python (comme la création dynamique de méthodes) peuvent ne pas fonctionner correctement avec les classes qui utilisent `__slots__`.
- Moins intuitif : Le comportement des classes avec `__slots__` peut être moins intuitif pour les développeurs Python moins expérimentés.
L'utilisation de `__slots__` est une *optimisation*. Elle doit être envisagée uniquement si vous avez un problème de performance (consommation mémoire excessive) et que vous avez identifié, grâce au profiling, que la création d'un grand nombre d'objets d'une classe spécifique est la cause du problème.
__slots__ et l'héritage
L'utilisation de `__slots__` avec l'héritage nécessite une attention particulière.
Si une classe fille hérite d'une classe qui définit `__slots__`, la classe fille *doit* également définir `__slots__` (même s'il est vide) si elle veut bénéficier des avantages de `__slots__`. Sinon, les instances de la classe fille auront à la fois un `__dict__` et les attributs définis dans `__slots__` de la classe parente, ce qui annule l'optimisation.
Exemple (problème si la classe fille ne définit pas `__slots__`):
class A:
__slots__ = ('x',)
class B(A): # Ne définit pas __slots__
def __init__(self, x, y):
super().__init__()
self.x = x
self.y = y
b = B(1, 2)
print(b.x) # 1
print(b.y) # 2
print(b.__dict__) # {'y': 2} # B a un __dict__ ! (malgré le __slots__ de A)Dans cet exemple, bien que `A` définisse `__slots__`, les instances de `B` ont quand même un `__dict__` (qui contient `y`).
Pour corriger cela, la classe fille doit définir `__slots__` :
class A:
__slots__ = ('x',)
class B(A):
__slots__ = ('y',) # Ajoute 'y' aux slots
def __init__(self, x, y):
super().__init__()
self.x = x
self.y = y
b = B(1, 2)
print(b.x) # 1
print(b.y) # 2
# print(b.__dict__) # AttributeError: 'B' object has no attribute '__dict__'Si la classe fille n'ajoute pas de nouveaux attributs, elle peut définir `__slots__ = ()`.
Il est important de bien comprendre les implications de `__slots__` sur l'héritage pour éviter les surprises.
Quand utiliser __slots__ ?
N'utilisez `__slots__` que si :
- Vous avez *mesuré* (avec un profileur) que la création d'un grand nombre d'instances d'une classe consomme une quantité significative de mémoire.
- Vous êtes *certain* que les objets de cette classe n'auront besoin que des attributs que vous avez spécifiés dans `__slots__` (pas d'ajout dynamique d'attributs).
- Vous comprenez les implications de `__slots__` sur l'héritage et les autres fonctionnalités de Python.
`__slots__` est une optimisation, pas une nécessité. Dans la plupart des cas, vous n'en aurez pas besoin. N'optimisez pas prématurément ! Utilisez `__slots__` uniquement si vous avez un problème de performance *avéré* et que vous avez identifié que la consommation mémoire des objets est la cause du problème.
Si vous n'êtes pas sûr, ne l'utilisez pas. La flexibilité des dictionnaires d'attributs par défaut est souvent préférable à une petite optimisation de la mémoire.