web-dev-qa-db-fra.com

Utilisation de __slots__?

Quel est le but de __slots__ dans Python - en particulier en ce qui concerne le moment où je souhaiterais l'utiliser et le moment non?

657
Jeb

En Python, quel est le but de __slots__ et quels sont les cas à éviter?

TLDR:

L'attribut spécial __slots__ vous permet d'indiquer explicitement quels attributs d'instance vous attendez de vos instances d'objet, avec les résultats attendus:

  1. plus rapide accès aux attributs.
  2. gain de place en mémoire.

Les économies d'espace sont de

  1. Stockage des références de valeur dans des emplacements au lieu de __dict__.
  2. Refuser la création de __dict__ et __weakref__ si les classes parent les refusent et que vous déclariez __slots__.

Mises en garde rapides

Petit bémol, vous ne devez déclarer une case particulière qu'une fois dans un arbre d'héritage. Par exemple:

class Base:
    __slots__ = 'foo', 'bar'

class Right(Base):
    __slots__ = 'baz', 

class Wrong(Base):
    __slots__ = 'foo', 'bar', 'baz'        # redundant foo and bar

Python ne fait pas d'objection lorsque vous vous trompez (cela devrait probablement), les problèmes pourraient ne pas se manifester autrement, mais vos objets prendront plus de place qu'ils ne le devraient autrement.

>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(64, 80)

La plus grande mise en garde concerne l'héritage multiple: il est impossible de combiner plusieurs "classes parent avec des emplacements non vides".

Pour respecter cette restriction, suivez les meilleures pratiques: Découpez l’abstraction de tous les parents sauf un ou tous ceux dont leur classe concrète et leur nouvelle classe concrète hériteront collectivement - en donnant à la/les abstraction (s) des emplacements vides (tout comme les classes de base abstraites de bibliothèque standard).

Voir la section sur l'héritage multiple ci-dessous pour un exemple.

Exigences:

  • Pour que les attributs nommés dans __slots__ soient réellement stockés dans des emplacements au lieu d'un __dict__, une classe doit hériter de object.

  • Pour empêcher la création d'un __dict__, vous devez hériter de object et toutes les classes de l'héritage doivent déclarer __slots__ et aucune d'entre elles ne peut avoir une entrée '__dict__'.

Il y a beaucoup de détails si vous souhaitez continuer à lire.

Pourquoi utiliser __slots__: Accès d'attribut plus rapide.

Le créateur de Python, Guido van Rossum, indique qu'il a en fait créé __slots__ pour un accès plus rapide aux attributs.

Il est trivial de démontrer un accès sensiblement plus rapide:

import timeit

class Foo(object): __slots__ = 'foo',

class Bar(object): pass

slotted = Foo()
not_slotted = Bar()

def get_set_delete_fn(obj):
    def get_set_delete():
        obj.foo = 'foo'
        obj.foo
        del obj.foo
    return get_set_delete

et

>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085

L'accès par créneau est presque 30% plus rapide sous Python 3.5 sous Ubuntu.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

Dans Python 2 sous Windows, je l’ai mesuré environ 15% plus vite.

Pourquoi utiliser __slots__: Economie de mémoire

Un autre objectif de __slots__ est de réduire l'espace mémoire occupé par chaque instance d'objet.

Ma propre contribution à la documentation énonce clairement les raisons derrière cela :

L'espace économisé sur __dict__ peut être important.

attributs SQLAlchemy beaucoup d'économies de mémoire pour __slots__.

Pour vérifier cela, en utilisant la distribution Anaconda de Python 2.7 sur Ubuntu Linux, avec guppy.hpy (alias heapy) et sys.getsizeof, la taille d'une instance de classe sans __slots__ déclaré, et rien d'autre, est de 64 octets. Cela n'inclut pas le __dict__. Merci Python pour une nouvelle évaluation, la __dict__ n'est apparemment pas appelée jusqu'à ce qu'elle soit référencée, mais les classes sans données sont généralement inutiles. Lorsqu'il est appelé, l'existence de l'attribut __dict__ est de 280 octets minimum.

En revanche, une instance de classe avec __slots__ déclarée comme étant () (aucune donnée) ne représente que 16 octets et 56 octets au total avec un élément dans les emplacements, 64 avec deux.

Pour Python 64 bits, j'illustre la consommation de mémoire en octets dans Python 2.7 et 3.6, pour __slots__ et __dict__ (aucun emplacement défini) pour chaque point où le dict se développe en 3.6. (sauf pour les attributs 0, 1 et 2):

       Python 2.7             Python 3.6
attrs  __slots__  __dict__*   __slots__  __dict__* | *(no slots defined)
none   16         56 + 272†   16         56 + 112† | †if __dict__ referenced
one    48         56 + 272    48         56 + 112
two    56         56 + 272    56         56 + 112
six    88         56 + 1040   88         56 + 152
11     128        56 + 1040   128        56 + 240
22     216        56 + 3344   216        56 + 408     
43     384        56 + 3344   384        56 + 752

Ainsi, malgré de plus petites tailles dans Python 3, nous voyons comment __slots__ redimensionne joliment pour que les instances nous épargnent de la mémoire. C’est une raison majeure pour laquelle vous voudriez utiliser __slots__ .

Pour que mes notes soient complètes, notez qu'il existe un coût unique par emplacement dans l'espace de noms de la classe de 64 octets dans Python 2 et de 72 octets dans Python 3, car utiliser des descripteurs de données tels que des propriétés, appelés "membres".

>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72

Démonstration de __slots__:

Pour refuser la création d'un __dict__, vous devez placer la sous-classe object:

class Base(object): 
    __slots__ = ()

maintenant:

>>> b = Base()
>>> b.a = 'a'
Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    b.a = 'a'
AttributeError: 'Base' object has no attribute 'a'

Ou sous-classe une autre classe qui définit __slots__

class Child(Base):
    __slots__ = ('a',)

et maintenant:

c = Child()
c.a = 'a'

mais:

>>> c.b = 'b'
Traceback (most recent call last):
  File "<pyshell#42>", line 1, in <module>
    c.b = 'b'
AttributeError: 'Child' object has no attribute 'b'

Pour autoriser la création de __dict__ lors de la sous-classification d'objets à créneaux, ajoutez simplement '__dict__' au __slots__ (notez que les emplacements sont ordonnés et que vous ne devez pas répéter les emplacements déjà dans les classes parentes):

class SlottedWithDict(Child): 
    __slots__ = ('__dict__', 'b')

swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'

et

>>> swd.__dict__
{'c': 'c'}

Ou vous n'avez même pas besoin de déclarer __slots__ dans votre sous-classe, et vous utiliserez toujours les slots des parents, sans toutefois restreindre la création d'un __dict__:

class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'

Et:

>>> ns.__dict__
{'b': 'b'}

Cependant, __slots__ peut entraîner des problèmes pour l'héritage multiple:

class BaseA(object): 
    __slots__ = ('a',)

class BaseB(object): 
    __slots__ = ('b',)

La création d'une classe enfant à partir des parents avec les deux emplacements non vides échouant:

>>> class Child(BaseA, BaseB): __slots__ = ()
Traceback (most recent call last):
  File "<pyshell#68>", line 1, in <module>
    class Child(BaseA, BaseB): __slots__ = ()
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

Si vous rencontrez ce problème, vous pourriez simplement supprimer __slots__ des parents, ou, si vous avez le contrôle sur les parents, leur attribuer des espaces vides, ou refactoriser des abstractions :

from abc import ABC

class AbstractA(ABC):
    __slots__ = ()

class BaseA(AbstractA): 
    __slots__ = ('a',)

class AbstractB(ABC):
    __slots__ = ()

class BaseB(AbstractB): 
    __slots__ = ('b',)

class Child(AbstractA, AbstractB): 
    __slots__ = ('a', 'b')

c = Child() # no problem!

Ajoutez '__dict__' à __slots__ pour obtenir une affectation dynamique:

class Foo(object):
    __slots__ = 'bar', 'baz', '__dict__'

et maintenant:

>>> foo = Foo()
>>> foo.boink = 'boink'

Donc, avec '__dict__' dans les slots, nous perdons certains des avantages de taille avec l'avantage d'avoir une affectation dynamique et d'avoir toujours des emplacements pour les noms que nous attendons.

Lorsque vous héritez d'un objet qui n'a pas été placé, vous obtenez le même type de sémantique lorsque vous utilisez __slots__ - les noms qui se trouvent dans __slots__ pointent sur des valeurs définies, tandis que les autres valeurs instance de __dict__.

Éviter __slots__ parce que vous souhaitez pouvoir ajouter des attributs à la volée n’est en fait pas une bonne raison. Ajoutez simplement "__dict__" à votre __slots__ si cela est nécessaire.

De même, vous pouvez ajouter __weakref__ à __slots__ explicitement si vous avez besoin de cette fonctionnalité.

Définir pour vider Tuple lors de la sous-classification d'un namedtuple:

La commande namedtuple crée des instances immuables très légères (essentiellement la taille des n-uplets), mais pour obtenir les avantages, vous devez le faire vous-même si vous les sous-classez:

from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
    """MyNT is an immutable and lightweight object"""
    __slots__ = ()

usage:

>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'

Et essayer d’attribuer un attribut inattendu déclenche une AttributeError car nous avons empêché la création de __dict__:

>>> nt.quux = 'quux'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'

Vous pouvez autoriser la création de __dict__ en laissant __slots__ = (), mais vous ne pouvez pas utiliser __slots__ non vide avec des sous-types de Tuple.

La plus grande mise en garde: l'héritage multiple

Même lorsque les créneaux horaires non vides sont identiques pour plusieurs parents, ils ne peuvent pas être utilisés ensemble:

class Foo(object): 
    __slots__ = 'foo', 'bar'
class Bar(object):
    __slots__ = 'foo', 'bar' # alas, would work if empty, i.e. ()

>>> class Baz(Foo, Bar): pass
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Error when calling the metaclass bases
    multiple bases have instance lay-out conflict

Utiliser un __slots__ vide dans le parent semble offrir la plus grande souplesse permettant à l’enfant de choisir d’empêcher ou d’autoriser (en ajoutant '__dict__' pour obtenir une affectation dynamique, voir la section ci-dessus) la création d'un __dict__:

class Foo(object): __slots__ = ()
class Bar(object): __slots__ = ()
class Baz(Foo, Bar): __slots__ = ('foo', 'bar')
b = Baz()
b.foo, b.bar = 'foo', 'bar'

Vous n'avez pas pour avoir des emplacements - donc si vous les ajoutez et les supprimez plus tard, cela ne devrait poser aucun problème.

Aller sur une branche ici : Si vous composez mixins ou utilisez classes de base abstraites , qui ne sont pas destinés à être instanciés, un __slots__ vide chez ces parents semble être la meilleure voie à suivre en termes de flexibilité pour les sous-classes.

Pour démontrer, commençons par créer une classe avec le code que nous aimerions utiliser sous héritage multiple

class AbstractBase:
    __slots__ = ()
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def __repr__(self):
        return f'{type(self).__name__}({repr(self.a)}, {repr(self.b)})'

Nous pourrions utiliser ce qui précède directement en héritant et en déclarant les créneaux attendus:

class Foo(AbstractBase):
    __slots__ = 'a', 'b'

Mais nous ne nous soucions pas de ça, c'est de l'héritage simple trivial, nous avons besoin d'une autre classe dont nous pourrions également hériter, peut-être avec un attribut bruyant:

class AbstractBaseC:
    __slots__ = ()
    @property
    def c(self):
        print('getting c!')
        return self._c
    @c.setter
    def c(self, arg):
        print('setting c!')
        self._c = arg

Maintenant, si les deux bases avaient des slots non vides, nous ne pourrions pas faire le dessous. (En fait, si nous le voulions, nous aurions pu donner AbstractBase logements non vides a et b, et les laisser en dehors de la déclaration ci-dessous - les laisser entrer aurait tort.

class Concretion(AbstractBase, AbstractBaseC):
    __slots__ = 'a b _c'.split()

Et maintenant, nous avons des fonctionnalités des deux via l'héritage multiple, et nous pouvons toujours refuser l'instance __dict__ et __weakref__:

>>> c = Concretion('a', 'b')
>>> c.c = c
setting c!
>>> c.c
getting c!
Concretion('a', 'b')
>>> c.d = 'd'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Concretion' object has no attribute 'd'

Autres cas à éviter:

  • Evitez-les lorsque vous voulez effectuer _ l'attribution __class__ avec une autre classe qui n'en a pas (et vous ne pouvez pas les ajouter) à moins que les dispositions des emplacements ne soient identiques. (Je suis très intéressé d'apprendre qui fait cela et pourquoi.)
  • Evitez-les si vous souhaitez sous-classer des structures de longueur variable telles que long, Tuple ou str et que vous souhaitez leur ajouter des attributs.
  • Evitez-les si vous insistez pour fournir des valeurs par défaut via des attributs de classe pour les variables d'instance.

Vous pourrez peut-être écarter d'autres mises en garde du reste du __slots__documentation (les documents de développement 3.7 sont les plus récents) , auxquels j'ai récemment apporté d'importantes contributions.

Critiques d'autres réponses

Les principales réponses actuelles citent des informations obsolètes, sont plutôt vagues et manquent à certains égards.

Ne "utilisez pas seulement __slots__ pour instancier beaucoup d'objets"

Je cite:

"Vous voudriez utiliser __slots__ si vous voulez instancier beaucoup (des centaines, des milliers) d'objets de la même classe."

Les classes de base abstraites, par exemple, du module collections, ne sont pas instanciées, mais __slots__ est déclaré pour elles.

Pourquoi?

Si un utilisateur souhaite refuser la création de __dict__ ou __weakref__, ces éléments ne doivent pas être disponibles dans les classes parentes.

__slots__ contribue à la réutilisation lors de la création d'interfaces ou de mixins.

Il est vrai que de nombreux utilisateurs Python n'écrivent pas à des fins de réutilisation, mais le cas échéant, l'option de refuser l'utilisation d'espace inutile est utile.

__slots__ n'interrompt pas le décapage

Lorsque vous choisissez un objet fendu, vous pouvez constater qu'il se plaint d'un TypeError trompeur:

>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled

Ceci est en fait incorrect. Ce message provient du protocole le plus ancien, le protocole par défaut. Vous pouvez sélectionner le dernier protocole avec l'argument -1. Dans Python2.7, il s'agirait de 2 (introduit dans la version 2.3), et en 3.6, de 4.

>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>

dans Python 2.7:

>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>

dans Python 3.6

>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>

Donc, je garderais cela à l'esprit, car c'est un problème résolu.

Critique de la réponse acceptée (jusqu'au 2 octobre 2016)

Le premier paragraphe est une explication moitié courte, moitié prédictive. Voici la seule partie qui répond réellement à la question

L'utilisation correcte de __slots__ consiste à économiser de l'espace dans les objets. Au lieu d'avoir un dict dynamique qui permet d'ajouter des attributs aux objets à tout moment, il existe une structure statique qui n'autorise pas les ajouts après la création. Cela évite la surcharge d’un dict pour chaque objet utilisant des slots

La seconde moitié est un vœu pieux, et off the mark:

Bien que cette optimisation soit parfois utile, il serait tout à fait inutile que l’interpréteur Python soit suffisamment dynamique pour ne nécessiter le dict que lorsqu’il y a eu des ajouts à l’objet.

Python fait quelque chose de similaire à cela, ne créant que le __dict__ lorsqu’on y accède, mais créer beaucoup d’objets sans données est assez ridicule.

Le deuxième paragraphe simplifie à l'excès et omet les raisons réelles d'éviter __slots__. Le texte ci-dessous est et non une véritable raison d'éviter les créneaux (pour les raisons réelles , voir le reste de ma réponse ci-dessus.):

Ils modifient le comportement des objets dotés d'emplacements de manière à ce qu'ils puissent être utilisés de manière abusive par les maniaques de contrôle et les marqueurs statiques.

Il poursuit ensuite en discutant d’autres moyens d’atteindre cet objectif pervers avec Python, sans discuter de tout ce qui a trait à __slots__.

Le troisième paragraphe est un vœu pieux. Ensemble, il s’agit pour la plupart de contenu hors propos que le répondeur n’a même pas écrit et contribue à fournir des munitions aux critiques du site.

Preuve d'utilisation de la mémoire

Créez des objets normaux et des objets à créneaux:

>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()

Instancier un million d'entre eux:

>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]

Inspecter avec guppy.hpy().heap():

>>> guppy.hpy().heap()
Partition of a set of 2028259 objects. Total size = 99763360 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0 1000000  49 64000000  64  64000000  64 __main__.Foo
     1     169   0 16281480  16  80281480  80 list
     2 1000000  49 16000000  16  96281480  97 __main__.Bar
     3   12284   1   987472   1  97268952  97 str
...

Accédez aux objets réguliers et à leur __dict__ et inspectez à nouveau:

>>> for f in foos:
...     f.__dict__
>>> guppy.hpy().heap()
Partition of a set of 3028258 objects. Total size = 379763480 bytes.
 Index  Count   %      Size    % Cumulative  % Kind (class / dict of class)
     0 1000000  33 280000000  74 280000000  74 dict of __main__.Foo
     1 1000000  33  64000000  17 344000000  91 __main__.Foo
     2     169   0  16281480   4 360281480  95 list
     3 1000000  33  16000000   4 376281480  99 __main__.Bar
     4   12284   0    987472   0 377268952  99 str
...

Ceci est cohérent avec l’histoire de Python, de types et classes d’unification dans Python 2.2

Si vous sous-classez un type intégré, de l'espace supplémentaire est automatiquement ajouté aux instances pour accueillir __dict__ et __weakrefs__. (Le __dict__ n'est pas initialisé tant que vous ne l'utilisez pas, vous ne devez donc pas vous inquiéter de l'espace occupé par un dictionnaire vide pour chaque instance que vous créez.) Si vous n'avez pas besoin de cet espace supplémentaire, vous pouvez ajouter l'expression "__slots__ = []" à votre classe.

816
Aaron Hall

Citant Jacob Hallen :

L'utilisation correcte de __slots__ consiste à économiser de l'espace dans les objets. Au lieu d'avoir un dict dynamique qui permet d'ajouter des attributs aux objets à tout moment, il existe une structure statique qui n'autorise pas les ajouts après la création. [Cette utilisation de __slots__ élimine le surcoût d’un dict pour chaque objet.] Bien que cette optimisation soit parfois utile, il serait totalement inutile que l’interprète Python soit suffisamment dynamique pour n’exige le dict que lorsqu’il y a eu des ajouts à l’objet.

Malheureusement, les machines à sous ont un effet secondaire. Ils modifient le comportement des objets dotés d'emplacements de manière à ce qu'ils puissent être utilisés de manière abusive par les maniaques de contrôle et les marqueurs statiques. C’est mauvais, parce que les maniaques de contrôle devraient abuser des métaclasses et que les weenies de frappe statiques devraient abuser des décorateurs, puisqu’en Python, il ne devrait y avoir qu’une façon évidente de faire quelque chose.

Rendre CPython suffisamment intelligent pour gérer un gain de place sans __slots__ est une entreprise majeure, ce qui explique probablement pourquoi il ne figure pas (encore) dans la liste des modifications apportées à P3k.

262
Jeff Bauer

Vous voudriez utiliser __slots__ si vous voulez instancier beaucoup (des centaines, des milliers) d'objets de la même classe. __slots__ n'existe qu'en tant qu'outil d'optimisation de la mémoire.

Il est vivement déconseillé d'utiliser __slots__ pour limiter la création d'attributs. En règle générale, vous souhaitez l'éviter car cela casse la tâche, ainsi que d'autres fonctionnalités d'introspection de python.

120
Ryan

Chaque objet python a un attribut __dict__ qui est un dictionnaire contenant tous les autres attributs. par exemple. lorsque vous tapez self.attr python fait réellement self.__dict__['attr']. Comme vous pouvez l'imaginer, utiliser un dictionnaire pour stocker l'attribut nécessite un espace et un temps supplémentaires pour y accéder.

Cependant, lorsque vous utilisez __slots__, aucun objet créé pour cette classe n'aura d'attribut __dict__. Au lieu de cela, tous les attributs sont accessibles directement via des pointeurs.

Donc, si vous voulez une structure de style C plutôt qu'une classe à part entière, vous pouvez utiliser __slots__ pour réduire la taille des objets et réduire le temps d'accès aux attributs. Un bon exemple est une classe de points contenant les attributs x et y. Si vous voulez avoir beaucoup de points, vous pouvez essayer d'utiliser __slots__ afin de conserver un peu de mémoire.

58
Suraj

En plus des autres réponses, voici un exemple d'utilisation de __slots__:

>>> class Test(object):   #Must be new-style class!
...  __slots__ = ['x', 'y']
... 
>>> pt = Test()
>>> dir(pt)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', 
 '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', 
 '__repr__', '__setattr__', '__slots__', '__str__', 'x', 'y']
>>> pt.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: x
>>> pt.x = 1
>>> pt.x
1
>>> pt.z = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute 'z'
>>> pt.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Test' object has no attribute '__dict__'
>>> pt.__slots__
['x', 'y']

Donc, pour implémenter __slots__, il ne faut qu'une ligne supplémentaire (et faire de votre classe une classe de style nouveau si ce n'est déjà fait). De cette façon, vous pouvez réduire de 5 fois l'encombrement de la mémoire de ces classes , au prix de l'écriture d'un code pickle personnalisé, si et quand cela devient nécessaire.

20
Evgeni Sergeev

Les emplacements sont très utiles pour les appels de bibliothèque afin d'éliminer la "répartition de méthode nommée" lors d'appels de fonction. Ceci est mentionné dans le SWIG documentation . Pour les bibliothèques hautes performances qui souhaitent réduire le temps système pour les fonctions communément appelées utilisant des emplacements, c'est beaucoup plus rapide.

Maintenant, cela peut ne pas être directement lié à la question des PO. Cela concerne davantage la construction d'extensions que l'utilisation de la syntaxe slots sur un objet. Mais cela aide à compléter le tableau pour l'utilisation des créneaux horaires et une partie du raisonnement qui les sous-tend.

11
Demolishun

Un attribut d'une instance de classe a 3 propriétés: l'instance, le nom de l'attribut et la valeur de l'attribut.

Dans accès aux attributs habituels , l'instance fait office de dictionnaire et le nom de l'attribut sert de clé dans ce dictionnaire à la recherche valeur ajoutée.

instance (attribut) -> valeur

Dans __ slots__ access , le nom de l'attribut fait office de dictionnaire et l'instance sert de clé dans le dictionnaire. valeur.

attribut (instance) -> valeur

Dans modèle de poids mouche , le nom de l'attribut sert de dictionnaire et la valeur agit comme clé dans ce dictionnaire. l'instance.

attribut (valeur) -> instance

7

Une autre utilisation quelque peu obscure de __slots__ consiste à ajouter des attributs à un proxy d'objet à partir du package ProxyTypes, faisant autrefois partie du projet PEAK. Son ObjectWrapper vous permet de créer un proxy pour un autre objet, mais d'intercepter toutes les interactions avec l'objet traité par proxy. Il n’est pas très utilisé (et pas de support Python 3), mais nous l’avons utilisé pour implémenter un wrapper de blocage thread-safe autour d’une implémentation asynchrone basée sur tornado qui renvoie tous les accès à l’objet mandaté via le ioloop, en utilisant les objets thread-safe concurrent.Future pour synchroniser et renvoyer les résultats.

Par défaut, tout attribut ayant accès à l'objet proxy vous donnera le résultat de l'objet traité par proxy. Si vous devez ajouter un attribut à l'objet proxy, vous pouvez utiliser __slots__.

from peak.util.proxies import ObjectWrapper

class Original(object):
    def __init__(self):
        self.name = 'The Original'

class ProxyOriginal(ObjectWrapper):

    __slots__ = ['proxy_name']

    def __init__(self, subject, proxy_name):
        # proxy_info attributed added directly to the
        # Original instance, not the ProxyOriginal instance
        self.proxy_info = 'You are proxied by {}'.format(proxy_name)

        # proxy_name added to ProxyOriginal instance, since it is
        # defined in __slots__
        self.proxy_name = proxy_name

        super(ProxyOriginal, self).__init__(subject)

if __== "__main__":
    original = Original()
    proxy = ProxyOriginal(original, 'Proxy Overlord')

    # Both statements print "The Original"
    print "original.name: ", original.name
    print "proxy.name: ", proxy.name

    # Both statements below print 
    # "You are proxied by Proxy Overlord", since the ProxyOriginal
    # __init__ sets it to the original object 
    print "original.proxy_info: ", original.proxy_info
    print "proxy.proxy_info: ", proxy.proxy_info

    # prints "Proxy Overlord"
    print "proxy.proxy_name: ", proxy.proxy_name
    # Raises AttributeError since proxy_name is only set on 
    # the proxy object
    print "original.proxy_name: ", proxy.proxy_name
2
NeilenMarais

Un exemple très simple d'attribut __slot__.

Problème: Sans __slots__

Si je n'ai pas d'attribut __slot__ dans ma classe, je peux ajouter de nouveaux attributs à mes objets.

class Test:
    pass

obj1=Test()
obj2=Test()

print(obj1.__dict__)  #--> {}
obj1.x=12
print(obj1.__dict__)  # --> {'x': 12}
obj1.y=20
print(obj1.__dict__)  # --> {'x': 12, 'y': 20}

obj2.x=99
print(obj2.__dict__)  # --> {'x': 99}

Si vous regardez l'exemple ci-dessus, vous pouvez voir que obj1 et obj2 ont leurs propres attributs x et y et python a également créé un attribut dict pour chaque objet (obj1 et obj2).

Supposons que si ma classe Test a des milliers de tels objets? La création d'un attribut supplémentaire dict pour chaque objet entraînera beaucoup de temps système (mémoire, puissance de calcul, etc.) dans mon code.

Solution: avec __slots__

Maintenant, dans l'exemple suivant, ma classe Test contient l'attribut __slots__. Maintenant, je ne peux pas ajouter de nouveaux attributs à mes objets (sauf l'attribut x) et python ne crée plus d'attribut dict. Cela élimine la surcharge pour chaque objet, ce qui peut devenir important si vous avez plusieurs objets.

class Test:
    __slots__=("x")

obj1=Test()
obj2=Test()
obj1.x=12
print(obj1.x)  # --> 12
obj2.x=99
print(obj2.x)  # --> 99

obj1.y=28
print(obj1.y)  # --> AttributeError: 'Test' object has no attribute 'y'
2
N Randhawa

Vous avez - essentiellement - aucune utilisation de __slots__.

Pour le moment où vous pensez avoir besoin de __slots__, vous souhaitez réellement utiliser les modèles de conception Léger ou Poids léger. Dans certains cas, vous ne souhaitez plus utiliser uniquement des objets Python. Au lieu de cela, vous voulez un Python objet ressemblant à un objet autour d'un tableau, d'une structure ou d'un tableau numpy.

class Flyweight(object):

    def get(self, theData, index):
        return theData[index]

    def set(self, theData, index, value):
        theData[index]= value

Le wrapper semblable à une classe n'a pas d'attributs - il fournit uniquement des méthodes qui agissent sur les données sous-jacentes. Les méthodes peuvent être réduites à des méthodes de classe. En effet, il pourrait être réduit aux seules fonctions opérant sur le tableau de données sous-jacent.

1
S.Lott

La question initiale portait sur les cas d'utilisation générale, pas seulement sur la mémoire. Il convient donc de mentionner ici que vous obtenez également un meilleur résultat performance lors de l’instanciation de grandes quantités d’objets - intéressant, par exemple. lors de l'analyse de documents volumineux en objets ou à partir d'une base de données.

Voici une comparaison de la création d'arborescences d'objets avec un million d'entrées, en utilisant des emplacements et sans emplacements. À titre de référence, également les performances lors de l'utilisation de plans simples pour les arbres (Py2.7.10 sur OSX):

********** RUN 1 **********
1.96036410332 <class 'css_tree_select.element.Element'>
3.02922606468 <class 'css_tree_select.element.ElementNoSlots'>
2.90828204155 dict
********** RUN 2 **********
1.77050495148 <class 'css_tree_select.element.Element'>
3.10655999184 <class 'css_tree_select.element.ElementNoSlots'>
2.84120798111 dict
********** RUN 3 **********
1.84069895744 <class 'css_tree_select.element.Element'>
3.21540498734 <class 'css_tree_select.element.ElementNoSlots'>
2.59615707397 dict
********** RUN 4 **********
1.75041103363 <class 'css_tree_select.element.Element'>
3.17366290092 <class 'css_tree_select.element.ElementNoSlots'>
2.70941114426 dict

Classes de test (ident, en dehors des slots):

class Element(object):
    __slots__ = ['_typ', 'id', 'parent', 'childs']
    def __init__(self, typ, id, parent=None):
        self._typ = typ
        self.id = id
        self.childs = []
        if parent:
            self.parent = parent
            parent.childs.append(self)

class ElementNoSlots(object): (same, w/o slots)

testcode, mode commenté:

na, nb, nc = 100, 100, 100
for i in (1, 2, 3, 4):
    print '*' * 10, 'RUN', i, '*' * 10
    # tree with slot and no slot:
    for cls in Element, ElementNoSlots:
        t1 = time.time()
        root = cls('root', 'root')
        for i in xrange(na):
            ela = cls(typ='a', id=i, parent=root)
            for j in xrange(nb):
                elb = cls(typ='b', id=(i, j), parent=ela)
                for k in xrange(nc):
                    elc = cls(typ='c', id=(i, j, k), parent=elb)
        to =  time.time() - t1
        print to, cls
        del root

    # ref: tree with dicts only:
    t1 = time.time()
    droot = {'childs': []}
    for i in xrange(na):
        ela =  {'typ': 'a', id: i, 'childs': []}
        droot['childs'].append(ela)
        for j in xrange(nb):
            elb =  {'typ': 'b', id: (i, j), 'childs': []}
            ela['childs'].append(elb)
            for k in xrange(nc):
                elc =  {'typ': 'c', id: (i, j, k), 'childs': []}
                elb['childs'].append(elc)
    td = time.time() - t1
    print td, 'dict'
    del droot
0
Red Pill