Comme d'autres l'ont mentionné, la seule raison pour laquelle on a défini __slots__
est d'économiser de la mémoire, lorsque vous avez des objets simples avec un ensemble prédéfini d'attributs et que vous ne voulez pas que chacun d'entre eux se trimballe avec un dictionnaire. Cela n'a de sens que pour les classes dont vous prévoyez d'avoir de nombreuses instances, bien sûr.
Les économies ne sont peut-être pas immédiatement évidentes - considérez... :
>>> class NoSlots(object): pass
...
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
...
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36
Il semblerait donc que la taille des fentes soit la suivante plus grand que la taille sans créneau ! Mais c'est une erreur, car sys.getsizeof
ne tient pas compte des "contenus d'objets" tels que le dictionnaire :
>>> sys.getsizeof(n.__dict__)
140
Puisque la dictée seule prend 140 octets, il est clair que l'objet "32 octets" n
est censé prendre ne tiennent pas compte de tout ce qui est impliqué dans chaque cas. Vous pouvez faire un meilleur travail avec des extensions tierces telles que pympler :
>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288
Cela montre beaucoup plus clairement l'empreinte mémoire qui est sauvée par __slots__
pour un objet simple comme celui-ci, c'est un peu moins de 200 octets, soit près des 2/3 de l'empreinte globale de l'objet. Étant donné qu'aujourd'hui, un mégaoctet de plus ou de moins n'a pas vraiment d'importance pour la plupart des applications, cela vous indique également que __slots__
ne vaut pas la peine si vous n'avez que quelques milliers d'instances à la fois, mais pour des millions d'instances, cela fait une différence très importante. Vous pouvez également obtenir un gain de vitesse microscopique (en partie dû à une meilleure utilisation du cache pour les petits objets avec la fonction __slots__
) :
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.332 usec per loop
mais cela dépend quelque peu de la version de Python (ce sont les chiffres que je mesure de manière répétée avec la version 2.5 ; avec la version 2.6, je constate un avantage relatif plus important en faveur de __slots__
pour paramètre un attribut, mais pas du tout, en fait un minuscule dis avantage, pour obtenir le).
Maintenant, en ce qui concerne l'héritage : pour qu'une instance soit sans dicton, tous Les classes situées en amont de sa chaîne d'héritage doivent également avoir des instances sans dicton. Les classes avec des instances sans dictée sont celles qui définissent __slots__
plus la plupart des types intégrés (les types intégrés dont les instances ont des dicts sont ceux sur lesquels vous pouvez définir des attributs arbitraires, tels que des fonctions). Les chevauchements dans les noms de slots ne sont pas interdits, mais ils sont inutiles et gaspillent de la mémoire, puisque les slots sont hérités :
>>> class A(object): __slots__='a'
...
>>> class AB(A): __slots__='b'
...
>>> ab=AB()
>>> ab.a = ab.b = 23
>>>
comme vous le voyez, vous pouvez définir l'attribut a
sur un AB
instance -- AB
ne définit lui-même que l'emplacement b
mais il hérite de la fente a
de A
. La répétition de l'emplacement hérité n'est pas interdite :
>>> class ABRed(A): __slots__='a','b'
...
>>> abr=ABRed()
>>> abr.a = abr.b = 23
mais gaspille un peu de mémoire :
>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96
donc il n'y a pas vraiment de raison de le faire.