Question Utilisation de __slots__?


Quel est le but de __slots__ en Python - surtout par rapport à quand voudrais-je l'utiliser et quand pas?


504
2018-01-23 05:37


origine


Réponses:


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

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 l'accès par attribut.
  2. économies d'espace en mémoire.

L'économie d'espace provient de

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

Avertissements rapides

Petite mise en garde, vous ne devez déclarer un emplacement particulier qu'une seule 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 s'oppose pas lorsque vous vous trompez (cela devrait probablement être le cas), les problèmes peuvent ne pas se manifester autrement, mais vos objets occuperont plus d'espace qu'ils ne le devraient autrement.

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

La plus grande réserve est pour l'héritage multiple - plusieurs "classes parentes avec des emplacements non vides" ne peuvent pas être combinées.

Pour accommoder cette restriction, suivez les meilleures pratiques: Comptez toutes les abstractions sauf une ou la totalité de la classe concrète, héritées respectivement de votre classe concrète et de votre nouvelle classe concrète - en donnant les abstractions aux emplacements vides (comme les classes de base abstraites du bibliothèque standard).

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

Exigences:

  • Pour avoir des attributs nommés dans __slots__ pour être effectivement stocké dans des fentes 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 aucun d'entre eux ne peut avoir un '__dict__' entrée.

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

Pourquoi utiliser __slots__: Accès aux attributs plus rapide.

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

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

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 fente est presque 30% plus rapide dans Python 3.5 sur Ubuntu.

>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342

En Python 2 sur Windows, je l'ai mesuré environ 15% plus vite.

Pourquoi utiliser __slots__: Économies de mémoire

Un autre but de __slots__ est de réduire l'espace en mémoire que chaque instance d'objet occupe.

Ma propre contribution à la documentation énonce clairement les raisons de cette:

L'espace économisé sur l'utilisation __dict__ peut être significatif.

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 (aka heapy) et sys.getsizeof, la taille d'une instance de classe sans __slots__ déclaré, et rien d'autre, est de 64 octets. Ça fait ne pas inclure le __dict__. Merci Python pour une nouvelle évaluation paresseuse, le __dict__n'est apparemment pas appelé à l'existence jusqu'à ce qu'il soit référencé, mais les classes sans données sont généralement inutiles. Lorsqu'on l'appelle à l'existence, le __dict__ attribut est un minimum de 280 octets en plus.

En revanche, une instance de classe avec __slots__ déclaré être () (pas de données) est seulement 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__ (pas d'emplacements définis) pour chaque point où le dict croît 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

Donc, malgré des dicts plus petits dans Python 3, on voit bien __slots__ échelle pour les instances pour nous faire économiser de la mémoire, et c'est une raison majeure que vous souhaitez utiliser __slots__.

Juste pour l'exhaustivité de mes notes, notez qu'il y a un coût unique par emplacement dans l'espace de noms de 64 octets en Python 2 et 72 octets en Python 3, car les slots utilisent des descripteurs de données comme les propriétés, appelés "membres".

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

Démonstration de __slots__:

Nier la création d'un __dict__, vous devez sous-classer object:

class Base(object): 
    __slots__ = ()

à présent:

>>> 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'

Autoriser __dict__ création tout en sous-classant les objets fendus, il suffit d'ajouter '__dict__' au __slots__ (Notez que les emplacements sont classés et que vous ne devez pas répéter les emplacements déjà présents 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 machines à sous des parents, mais ne limiterez pas 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 causer des problèmes pour l'héritage multiple:

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

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

Car la création d'une classe enfant à partir des parents avec les deux emplacements non vides échoue:

>>> 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 pourrait il suffit de retirer __slots__ des parents, ou si vous avez le contrôle des parents, donnez-leur des emplacements vides, ou réfutez-vous à 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!

Ajouter '__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 machines à sous, nous perdons une partie des avantages en termes de taille grâce à une affectation dynamique et des emplacements pour les noms auxquels nous nous attendons.

Lorsque vous héritez d'un objet qui n'est pas fendu, vous obtenez le même type de sémantique lorsque vous utilisez __slots__ - Les noms qui sont dans __slots__ pointer vers des valeurs fendues, tandis que toutes les autres valeurs sont placées dans les instances de l'instance. __dict__.

Éviter __slots__ parce que vous voulez être capable d’ajouter des attributs à la volée n’est pas une bonne raison "__dict__" à ton __slots__ si cela est requis

Vous pouvez également ajouter __weakref__ à __slots__ explicitement si vous avez besoin de cette fonctionnalité.

Définir pour vider le tuple lors du sous-classement d'un namedtuple:

Les builttuple builtin font des instances immuables qui sont très légères (essentiellement, la taille des tuples) 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 soulève un AttributeError parce que 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'

Toi pouvez permettre __dict__ création en laissant tomber __slots__ = (), mais vous ne pouvez pas utiliser non vide __slots__ avec des sous-types de tuple.

Plus grande mise en garde: héritage multiple

Même lorsque les emplacements 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

En utilisant un vide __slots__ dans le parent semble offrir le plus de flexibilité, permettre à l'enfant de choisir d'empêcher ou de permettre (en ajoutant '__dict__' pour obtenir l'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'

Tu n'as pas avoir avoir des slots - donc si vous les ajoutez et les supprimez plus tard, cela ne devrait pas poser de problème.

Sortir sur un membre ici: Si vous composez mixins ou en utilisant classes de base abstraites, qui ne sont pas destinés à être instanciés, un fichier vide __slots__ dans ces parents semble être le meilleur moyen d'aller en termes de flexibilité pour les sous-classeurs.

Pour démontrer, premièrement, créons une classe avec le code que nous aimerions utiliser sous l'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 emplacements attendus:

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

Mais nous ne nous en soucions pas, c'est un héritage simple trivial, nous avons besoin d'une autre classe dont nous pourrions 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 emplacements non vides, nous ne pourrions pas faire ce qui suit. (En fait, si nous le voulions, nous aurions pu donner AbstractBase emplacements non vides a et b, et les a laissés hors de la déclaration ci-dessous - les laisser dans serait faux):

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

Et maintenant, nous avons des fonctionnalités à la fois via l'héritage multiple, et peut toujours nier __dict__ et __weakref__ instanciation:

>>> 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 pour éviter les créneaux horaires:

  • Évitez-les lorsque vous voulez effectuer __class__ affectation avec une autre classe qui ne les a pas (et vous ne pouvez pas les ajouter), sauf si les dispositions de logement sont identiques. (Je suis très intéressé à apprendre qui fait cela et pourquoi.)
  • Evitez-les si vous voulez sous-classer des builtins de longueur variable comme long, tuple ou str, et que vous voulez leur ajouter des attributs.
  • Evitez-les si vous insistez pour fournir des valeurs par défaut via les attributs de classe pour les variables d'instance.

Vous pourrez peut-être faire ressortir d'autres mises en garde du reste de la __slots__  documentation (les docs de dev 3.7 sont les plus courants), à laquelle j'ai apporté d'importantes contributions récentes.

Critiques d'autres réponses

Les réponses les plus récentes citent des informations périmées et sont assez ondulées et manquent d'une certaine importance.

Ne pas "seulement utiliser __slots__ lors de l'instanciation de beaucoup d'objets "

Je cite:

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

Des classes de base abstraites, par exemple, du collections module, ne sont pas encore instanciés __slots__ sont déclarés pour eux.

Pourquoi?

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

__slots__contribue à la réutilisabilité lors de la création d'interfaces ou de mixins.

Il est vrai que beaucoup d'utilisateurs de Python n'écrivent pas pour la réutilisabilité, mais quand vous l'êtes, avoir la possibilité de refuser l'utilisation inutile de l'espace est précieux.

__slots__ ne brise pas le décapage

Lorsque vous soulevez un objet à fente, vous pouvez trouver qu'il se plaint avec une erreur TypeError:

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

C'est en fait incorrect. Ce message provient du protocole le plus ancien, qui est le protocole par défaut. Vous pouvez sélectionner le dernier protocole avec le -1 argument. En Python 2.7 ce serait 2 (qui a été introduit en 2.3), et en 3.6 il est 4.

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

en Python 2.7:

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

en 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 appropriée de __slots__ est d'économiser de l'espace dans les objets. Au lieu d'avoir une 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 permet d'économiser la surcharge d'un dict pour chaque objet utilisant des slots

La deuxième moitié est un vœu pieux, et hors de la marque:

Bien que ce soit parfois une optimisation utile, il serait complètement inutile si l'interpréteur Python était suffisamment dynamique pour qu'il ne nécessite que le dict quand il y avait réellement des ajouts à l'objet.

Python fait quelque chose de similaire, créant seulement le __dict__ quand on y accède, mais créer beaucoup d'objets sans données est assez ridicule.

Le deuxième paragraphe simplifie trop et manque les raisons réelles d'éviter __slots__. Le ci-dessous est ne pas une vraie raison d'éviter les slots (pour réel raisons, voir le reste de ma réponse ci-dessus.):

Ils modifient le comportement des objets qui ont des emplacements d'une manière qui peut être abusée par les monstres de contrôle et les weenies de frappe statique.

Il continue ensuite à discuter d'autres façons d'accomplir cet objectif pervers avec Python, ne discutant de rien à voir avec __slots__.

Le troisième paragraphe est plus pieux. Ensemble, c'est surtout le contenu hors-marque que le répondeur n'a même pas écrit et contribue aux munitions pour les critiques du site.

Preuve d'utilisation de la mémoire

Créez des objets normaux et des objets à fente:

>>> 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éder aux objets réguliers et à leur __dict__ et inspecter à 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 Unification des types et des classes dans Python 2.2

Si vous sous-classez un type intégré, un espace supplémentaire est automatiquement ajouté aux instances pour s'adapter __dict__ et __weakrefs__. (Le __dict__ n'est pas initialisé jusqu'à ce que vous l'utilisiez, vous ne devriez donc pas vous soucier 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 la phrase "__slots__ = []"à votre classe.


525
2018-01-21 04:46



Citant Jacob Hallen:

L'utilisation appropriée de __slots__ est d'économiser de l'espace dans les objets. Au lieu d'avoir   une dict dynamique qui permet d'ajouter des attributs aux objets à tout moment,   il y a une structure statique qui ne permet pas les ajouts après la création.   [Cette utilisation de __slots__ élimine les frais généraux d'un dict pour chaque objet.] Bien que ce soit parfois une optimisation utile, ce serait complètement   inutile si l'interpréteur Python était assez dynamique pour qu'il   exige seulement la dict quand il y avait réellement des ajouts à l'objet.

Malheureusement, il y a un effet secondaire sur les slots. Ils modifient le comportement de   les objets qui ont des fentes d'une manière qui peut être abusée par les monstres de contrôle   et les pollinisations statiques. C'est mauvais, car les monstres de contrôle devraient   abuser des métaclasses et les weenies de typage statique devraient abuser   décorateurs, car en Python, il ne devrait y avoir qu'une seule façon évidente de faire quelque chose.

Rendre CPython assez intelligent pour gérer l'économie d'espace sans __slots__ est un majeur   entreprise, ce qui explique probablement pourquoi elle ne figure pas encore dans la liste des modifications pour P3k.


257
2018-01-23 05:54



Vous voudriez utiliser __slots__ si vous allez instancier beaucoup (des centaines, des milliers) d'objets de la même classe. __slots__ n'existe que comme un outil d'optimisation de la mémoire.

Il est fortement déconseillé d'utiliser __slots__ pour la création d'attributs contraignants, et en général, vous voulez l'éviter car il casse pickle, ainsi que d'autres fonctionnalités d'introspection de python.


117
2018-01-23 05:50



Chaque objet python a un __dict__ atttribute qui est un dictionnaire contenant tous les autres attributs. par exemple. quand vous tapez self.attr Python est en train de faire self.__dict__['attr']. Comme vous pouvez l'imaginer, l'utilisation d'un dictionnaire pour stocker un attribut prend un peu plus de place et de temps pour y accéder.

Cependant, lorsque vous utilisez __slots__, tout objet créé pour cette classe n'aura pas de __dict__ attribut. Au lieu de cela, tous les accès aux attributs se font 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 compacter la taille des objets et réduire le temps d'accès aux attributs. Un bon exemple est une classe Point contenant des attributs x & y. Si vous allez avoir beaucoup de points, vous pouvez essayer d'utiliser __slots__ afin de conserver un peu de mémoire.


54
2018-01-23 13:38



En plus des autres réponses, voici un exemple d'utilisation __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 mettre en œuvre __slots__, il ne prend qu'une ligne supplémentaire (et fait de votre classe une nouvelle classe si ce n'est déjà fait). De cette façon, vous pouvez réduire de 5 fois l'empreinte mémoire de ces classes, au détriment d'avoir à écrire le code pickle personnalisé, si et quand cela devient nécessaire.


17
2018-06-03 07:43



Les emplacements sont très utiles pour les appels de bibliothèque afin d'éliminer le "dispatch de méthode nommée" lors des appels de fonction. Ceci est mentionné dans le SWIG Documentation. Pour les bibliothèques de haute performance qui veulent réduire la surcharge de fonctions pour les fonctions communément appelées en utilisant les slots, c'est beaucoup plus rapide.

Cela peut ne pas être directement lié à la question des PO. Il est davantage lié à la construction d'extensions qu'à l'utilisation de fentes syntaxe sur un objet. Mais cela aide à compléter l'image pour l'utilisation des slots et certains des raisonnements derrière eux.


11
2017-11-25 03:06



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

Dans accès aux attributs réguliers, l'instance agit comme un dictionnaire et le nom de l'attribut agit comme la clé dans ce dictionnaire recherchant la valeur.

instance (attribut) -> valeur

Dans __slots__ accès, le nom de l'attribut agit comme dictionnaire et l'instance agit comme la clé dans le dictionnaire recherchant la valeur.

attribut (instance) -> valeur

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

attribut (valeur) -> instance


5
2018-06-04 22:09



Vous avez - essentiellement - pas d'utilité pour __slots__.

Pour le moment où vous pensez que vous pourriez avoir besoin __slots__, vous voulez vraiment utiliser Poids léger ou Poids mouche modèles de conception. Ce sont des cas où vous ne voulez plus utiliser d'objets purement Python. Au lieu de cela, vous voulez un wrapper de type objet Python autour d'un tableau, d'un tableau struct 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

L'encapsuleur de type classe n'a pas d'attribut - il ne fournit que des méthodes qui agissent sur les données sous-jacentes. Les méthodes peuvent être réduites aux méthodes de classe. En effet, il pourrait être réduit aux seules fonctions fonctionnant sur le tableau de données sous-jacent.


2
2018-01-23 11:15



Une autre utilisation un peu obscure de __slots__ est d'ajouter des attributs à un proxy d'objet à partir du package ProxyTypes, qui faisait auparavant partie du projet PEAK. Ses ObjectWrapper vous permet de proxy un autre objet, mais intercepter toutes les interactions avec l'objet mandaté. Il n'est pas très utilisé (et ne prend pas en charge Python 3), mais nous l'avons utilisé pour implémenter un wrapper bloquant les threads autour d'une implémentation asynchrone basée sur tornado, qui rebute tout accès à l'objet proxy via ioloop. concurrent.Future objets pour synchroniser et renvoyer des résultats.

Par défaut, tout attribut d'accès à l'objet proxy vous donnera le résultat de l'objet traité par proxy. Si vous devez ajouter un attribut sur l'objet proxy, __slots__ peut être utilisé.

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 __name__ == "__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
2018-02-07 20:56



Un exemple très simple de __slot__ attribut.

Problème: Sans __slots__

Si je n'ai pas __slot__ attribut 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 leur propre X et y attributs et python a également créé un dict attribut pour chaque objet (obj1 et obj2).

Supposons que ma classe Tester a des milliers de tels objets? Création d'un attribut supplémentaire dict pour chaque objet provoquera beaucoup de surcharge (mémoire, puissance de calcul, etc.) dans mon code.

Solution: Avec __slots__

Maintenant dans l'exemple suivant ma classe Tester contient __slots__ attribut. Maintenant, je ne peux pas ajouter de nouveaux attributs à mes objets (sauf attribut x) et python ne crée pas de dict attribut plus. Cela élimine les frais généraux pour chaque objet, ce qui peut devenir important si vous avez beaucoup d'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'

1
2017-11-02 09:18



La question initiale portait sur les cas d'utilisation générale, pas seulement sur la mémoire. Donc, il convient de mentionner ici que vous obtenez aussi mieux performance lors de l'instanciation de grandes quantités d'objets - par exemple intéressant lors de l'analyse de documents volumineux dans des 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 slots et sans slots. Comme une référence également la performance lors de l'utilisation de dicts plaine 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, appart from 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)

code de test, mode verbeux:

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
2018-01-12 18:42