87 votes

Python : Comment fonctionne l'héritage de __slots__ dans les sous-classes ?

Dans le Section de référence du modèle de données Python sur les slots il existe une liste de notes sur l'utilisation __slots__ . Les points 1 et 6 me laissent profondément perplexe, car ils semblent se contredire.

Premier point :

  • Lorsque l'on hérite d'une classe sans __slots__ le __dict__ l'attribut de cette classe sera toujours accessible, donc un __slots__ définition dans la sous-classe est n'a pas de sens.

Sixième point :

  • L'action d'un __slots__ est limitée à la classe où elle est définie. Par conséquent, les les sous-classes auront une __dict__ à moins qu'ils ne définissent également __slots__ (qui ne doit contenir que les noms des créneaux supplémentaires).

Il me semble que ces éléments pourraient être mieux formulés ou illustrés par un code, mais j'ai essayé de m'y retrouver et je suis toujours confus. Je comprends comment __slots__ sont censé être utilisé et j'essaie de mieux comprendre comment ils fonctionnent.

La question :

Quelqu'un peut-il m'expliquer en langage clair quelles sont les conditions d'héritage des slots lors de la sous-classification ?

(Des exemples de code simples seraient utiles mais pas nécessaires).

Merci !

137voto

Alex Martelli Points 330805

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.

23voto

Georg Schölly Points 63123
class WithSlots(object):
    __slots__ = "a_slot"

class NoSlots(object):       # This class has __dict__
    pass

Premier point

class A(NoSlots):            # even though A has __slots__, it inherits __dict__
    __slots__ = "a_slot"     # from NoSlots, therefore __slots__ has no effect

Sixième point

class B(WithSlots):          # This class has no __dict__
    __slots__ = "some_slot"

class C(WithSlots):          # This class has __dict__, because it doesn't
    pass                     # specify __slots__ even though the superclass does.

Vous n'aurez probablement pas besoin d'utiliser __slots__ Il s'agit uniquement d'économiser de la mémoire au prix d'une certaine flexibilité. À moins que vous n'ayez des dizaines de milliers d'objets, cela n'aura aucune importance.

2voto

De la réponse que vous avez liée :

La bonne utilisation de __slots__ est d'économiser de l'espace dans les objets. Au lieu d'avoir une dictée dynamique...

"Lorsque l'on hérite d'une classe sans __slots__ le __dict__ de cette classe sera toujours accessible", donc ajouter votre propre __slots__ ne peut pas empêcher les objets d'avoir un __dict__ et ne permet pas de gagner de l'espace.

La partie concernant __slots__ ne pas être hérité est un peu obtus. Rappelez-vous qu'il s'agit d'un attribut magique qui ne se comporte pas comme les autres attributs, puis relisez-le en disant que le comportement des slots magiques n'est pas hérité. (C'est vraiment tout ce qu'il y a à dire).

1voto

ilya n. Points 6610

Ma compréhension est la suivante :

  • classe X n'a pas __dict__ <-------> classe X et ses superclasses ont toutes __slots__ spécifié

  • dans ce cas, les créneaux horaires réels de la classe sont constitués de l'union des éléments suivants __slots__ déclarations pour X et ses superclasses ; le comportement est indéfini (et deviendra une erreur) si cette union n'est pas disjointe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X