Réponses:
En Python, à quoi sert
__slots__
et dans quels cas faut-il éviter cela?
L'attribut spécial __slots__
vous permet d'indiquer explicitement les attributs d'instance que vous attendez de vos instances d'objet, avec les résultats attendus:
L'économie d'espace est de
__dict__
.__dict__
et __weakref__
création si les classes parentes les refusent et que vous déclarez __slots__
.Petite mise en garde, vous ne devez déclarer un emplacement particulier qu'une seule fois dans une arborescence 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), les problèmes pourraient ne pas se manifester autrement, mais vos objets prendront plus d'espace qu'ils ne le devraient autrement. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
En effet, le descripteur d'emplacement de la base a un emplacement distinct de celui du mauvais. Cela ne devrait généralement pas se produire, mais cela pourrait:
>>> w = Wrong()
>>> w.foo = 'foo'
>>> Base.foo.__get__(w)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: foo
>>> Wrong.foo.__get__(w)
'foo'
La plus grande mise en garde concerne l'héritage multiple - plusieurs "classes parentes avec des emplacements non vides" ne peuvent pas être combinées.
Pour tenir compte de cette restriction, suivez les meilleures pratiques: factorisez toutes les abstractions sauf un ou tous les parents dont leur classe concrète respectivement et votre nouvelle classe concrète hériteront collectivement - en donnant aux abstractions des emplacements vides (tout comme les classes de base abstraites dans le bibliothèque standard).
Voir la section sur l'héritage multiple ci-dessous pour un exemple.
Pour que les attributs nommés __slots__
soient réellement stockés dans des emplacements au lieu de a __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 d' '__dict__'
entrée.
Il y a beaucoup de détails si vous souhaitez continuer à lire.
__slots__
: Accès plus rapide aux attributs.Le créateur de Python, Guido van Rossum, déclare qu'il a réellement créé __slots__
pour un accès aux attributs plus rapide.
Il est trivial de démontrer un accès plus rapide et 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 fendu est presque 30% plus rapide en Python 3.5 sur Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
Dans Python 2 sur Windows, je l'ai mesuré environ 15% plus rapidement.
__slots__
: Économies de mémoireUn autre objectif de __slots__
est de réduire l'espace en mémoire occupé par chaque instance d'objet.
Ma propre contribution à la documentation indique clairement les raisons de cela :
L'espace économisé sur l'utilisation
__dict__
peut être important.
SQLAlchemy attribue de nombreuses économies de mémoire à __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 encore Python pour l'évaluation paresseuse, le __dict__
n'est apparemment pas appelé jusqu'à ce qu'il soit référencé, mais les classes sans données sont généralement inutiles. Lorsqu'il est appelé, cet __dict__
attribut comporte en outre au moins 280 octets.
En revanche, une instance de classe avec __slots__
déclarée être ()
(pas de données) ne fait 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 en Python 2.7 et 3.6, pour __slots__
et __dict__
(aucun emplacement défini) 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, en dépit de petits dict dans Python 3, nous voyons à quel point __slots__
les instances sont bien adaptées pour nous économiser de la mémoire, et c'est une raison majeure que vous voudriez utiliser __slots__
.
Juste pour l'intégralité de mes notes, notez qu'il y a un coût unique par emplacement dans l'espace de noms de la classe de 64 octets en Python 2 et 72 octets en Python 3, car les emplacements utilisent des descripteurs de données comme des propriétés, appelés "membres".
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Pour refuser la création d'un __dict__
, vous devez sous object
- classer :
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 permettre la __dict__
création tout en sous-classant les objets fendus, ajoutez simplement '__dict__'
à __slots__
(notez que les emplacements sont ordonnés, et vous ne devez pas répéter les emplacements qui sont 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 des emplacements des parents, mais ne restreignez 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, cela __slots__
peut entraîner des problèmes d'héritage multiple:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Parce que la création d'une classe enfant à partir de 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 pouvez simplement supprimer __slots__
des parents, ou si vous avez le contrôle des parents, leur donner des emplacements vides ou refactoriser les 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!
'__dict__'
à __slots__
pour obtenir une affectation dynamique:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
et maintenant:
>>> foo = Foo()
>>> foo.boink = 'boink'
Donc, avec les '__dict__'
slots, nous perdons certains des avantages de taille avec l'avantage d'avoir une affectation dynamique et d'avoir toujours des slots pour les noms que 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__
- des noms qui __slots__
pointent vers des valeurs fendues, tandis que toutes les autres valeurs sont placées dans l'instance __dict__
.
Éviter __slots__
parce que vous voulez être en mesure d'ajouter des attributs à la volée n'est en fait pas une bonne raison - ajoutez-le simplement si "__dict__"
vous en avez __slots__
besoin.
Vous pouvez également ajouter __weakref__
à __slots__
explicitement si vous avez besoin de cette fonctionnalité.
Le builtin namedtuple crée 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-classe:
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
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 __dict__
création en laissant de côté __slots__ = ()
, mais vous ne pouvez pas utiliser non vide __slots__
avec des sous - types de tuple.
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
L'utilisation d'un vide __slots__
dans le parent semble offrir la plus grande flexibilité, 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 besoin d'avoir des emplacements - donc si vous les ajoutez et les supprimez plus tard, cela ne devrait pas poser de problème.
Sortir sur une branche ici : Si vous composez des mixins ou utilisez des classes de base abstraites , qui ne sont pas destinées à être instanciées, un vide __slots__
dans ces parents semble être la meilleure voie à suivre en termes de flexibilité pour les sous-classes.
Pour démontrer, tout d'abord, créons une classe avec du 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 emplacements attendus:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Mais cela nous importe peu, c'est un 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 emplacements non vides, nous ne pourrions pas faire ce qui suit. (En fait, si nous le voulions, nous aurions pu donner des AbstractBase
emplacements non vides a et b, et les laisser en dehors de la déclaration ci-dessous - les laisser dedans serait faux):
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 __dict__
et __weakref__
instancier:
>>> 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'
__class__
affectation avec une autre classe qui ne les a pas (et vous ne pouvez pas les ajouter) sauf si les dispositions des emplacements sont identiques. (Je suis très intéressé à savoir qui fait cela et pourquoi.)Vous pourrez peut-être mettre en évidence d'autres mises en garde du reste de la __slots__
documentation ( les documents de développement 3.7 sont les plus récents) , auxquels j'ai apporté d'importantes contributions récentes.
Les meilleures réponses actuelles citent des informations obsolètes et sont assez ondulées à la main et manquent la marque de certaines manières importantes.
__slots__
lors de l'instanciation de nombreux objets"Je cite:
"Vous voudriez utiliser
__slots__
si vous allez instancier beaucoup (des centaines, des milliers) d'objets de la même classe."
Les classes de base abstraites, par exemple, du collections
module, ne sont pas instanciées, mais __slots__
sont déclarées pour elles.
Pourquoi?
Si un utilisateur souhaite refuser __dict__
ou __weakref__
créer, ces éléments 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 de nombreux utilisateurs de Python n'écrivent pas pour la réutilisation, mais lorsque vous l'êtes, avoir la possibilité de refuser l'utilisation inutile de l'espace est précieux.
__slots__
ne casse pas le décapageLorsque vous décapez un objet à fente, vous pouvez trouver qu'il se plaint d'un trompeur 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 l' -1
argument. En Python 2.7, ce serait 2
(qui a été introduit en 2.3), et en 3.6 c'est le cas 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>
Je garderais donc cela à l'esprit, car c'est un problème résolu.
Le premier paragraphe est moitié explication courte, moitié prédictive. Voici la seule partie qui répond réellement à la question
La bonne utilisation de
__slots__
est d'é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 économise les frais généraux d'un dict pour chaque objet qui utilise des emplacements
La seconde moitié est un vœu pieux, et hors de la marque:
Bien que cela soit parfois une optimisation utile, il serait complètement inutile si l'interpréteur Python était suffisamment dynamique pour qu'il ne nécessite la dictée que lorsqu'il y avait effectivement des ajouts à l'objet.
Python fait en fait quelque chose de similaire à cela, créant uniquement le __dict__
quand il est accédé, mais créer beaucoup d'objets sans données est assez ridicule.
Le deuxième paragraphe simplifie à l'excès et manque des raisons réelles à éviter __slots__
. Ce qui suit n'est pas une vraie raison pour éviter les créneaux horaires (pour des raisons réelles , voir le reste de ma réponse ci-dessus.):
Ils changent le comportement des objets qui ont des emplacements d'une manière qui peut être abusée par des monstres de contrôle et des caractères de frappe statiques.
Il continue ensuite à discuter d'autres façons d'accomplir cet objectif pervers avec Python, sans discuter de quoi que ce soit à voir avec __slots__
.
Le troisième paragraphe est un vœu pieux. Ensemble, il s'agit principalement de contenu hors du commun que le répondeur n'a même pas rédigé et contribue à des munitions pour les critiques du site.
Créez des objets normaux et des objets fendus:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Instanciez un million d'entre eux:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Inspectez 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, à partir des types et classes Unifying dans Python 2.2
Si vous sous-classe un type intégré, un 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 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__ = []
" à ta classe.
__slots__
. Sérieusement! Je vous remercie!
__slots__
environ un an: github.com/python/cpython/pull/1819/files
Citant Jacob Hallen :
La bonne utilisation de
__slots__
est d'é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 les frais généraux d'un dict pour chaque objet.] Bien qu'il s'agisse parfois d'une optimisation utile, il serait complètement inutile si l'interpréteur Python était suffisamment dynamique pour ne nécessiter le dict que lorsqu'il y avait effectivement des ajouts au objet.Malheureusement, les machines à sous ont un effet secondaire. Ils changent le comportement des objets qui ont des emplacements d'une manière qui peut être abusée par des monstres de contrôle et des caractères de frappe statiques. C'est mauvais, car les monstres de contrôle devraient abuser des métaclasses et les caractères de frappe statiques devraient abuser des 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'espace sans,
__slots__
est une entreprise majeure, c'est probablement pourquoi il n'est pas (encore) sur la liste des changements pour P3k.
__slots__
ne résout pas les mêmes problèmes que la frappe statique. Par exemple, en C ++, ce n'est pas la déclaration d'une variable membre qui est restreinte, c'est l'affectation d'un type involontaire (et d'un compilateur imposé) à cette variable. Je n'approuve pas l'utilisation de __slots__
, juste intéressé par la conversation. Merci!
Vous voudrez utiliser __slots__
si vous allez instancier beaucoup (des centaines, des milliers) d'objets de la même classe. __slots__
n'existe que comme outil d'optimisation de la mémoire.
Il est fortement déconseillé d'utiliser __slots__
pour limiter la création d'attributs.
Le décapage d'objets avec __slots__
ne fonctionnera pas avec le protocole de décapage par défaut (le plus ancien); il est nécessaire de spécifier une version ultérieure.
Certaines autres fonctionnalités d'introspection de python peuvent également être affectées.
Chaque objet python a un __dict__
attribut qui est un dictionnaire contenant tous les autres attributs. par exemple, lorsque 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 l'attribut prend un peu plus de temps et d'espace pour y accéder.
Cependant, lorsque vous utilisez __slots__
, tout objet créé pour cette classe n'aura pas d' __dict__
attribut. Au lieu de cela, tout accès aux attributs se fait 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 les attributs x & y. Si vous allez avoir beaucoup de points, vous pouvez essayer de les utiliser __slots__
pour économiser de la mémoire.
__slots__
défini n'est pas comme une structure de style C. Il existe un dictionnaire de niveau classe mappant les noms d'attribut aux index, sinon ce ne serait pas possible: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Je pense vraiment que cette réponse devrait être clarifiée (je peux le faire si vous le souhaitez). De plus, je ne suis pas certain que ce instance.__hidden_attributes[instance.__class__[attrname]]
soit plus rapide que instance.__dict__[attrname]
.
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 l'implémenter __slots__
, cela ne prend qu'une ligne supplémentaire (et faire de votre classe une classe de nouveau style si ce n'est pas déjà fait). De cette façon, vous pouvez réduire l'empreinte mémoire de ces classes de 5 fois , au détriment d'avoir à écrire du code de pickle personnalisé, si et quand cela devient nécessaire.
Les emplacements sont très utiles pour les appels de bibliothèque pour éliminer la «répartition de méthode nommée» lors des appels de fonction. Ceci est mentionné dans la documentation SWIG . Pour les bibliothèques hautes performances qui souhaitent réduire la surcharge de fonctions pour les fonctions communément appelées utilisant des slots, c'est beaucoup plus rapide.
Maintenant, cela peut ne pas être directement lié à la question des PO. Elle est plus liée à la création d'extensions qu'à l'utilisation de la syntaxe des emplacements sur un objet. Mais cela aide à compléter l'image de l'utilisation des créneaux horaires et certains des motifs derrière eux.
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 l' accès normal aux attributs , l'instance agit comme un dictionnaire et le nom de l'attribut agit comme la clé de ce dictionnaire en recherchant la valeur.
instance (attribut) -> valeur
Dans l' accès __slots__ , le nom de l'attribut agit comme le dictionnaire et l'instance agit comme la clé dans la valeur de recherche du dictionnaire.
attribut (instance) -> valeur
Dans le modèle flyweight , le nom de l'attribut agit comme le dictionnaire et la valeur agit comme la clé dans ce dictionnaire qui recherche l'instance.
attribut (valeur) -> instance
__slots__
?
Un exemple très simple d' __slot__
attribut.
__slots__
Si je n'ai pas d' __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 leurs propres attributs x et y et python a également créé un dict
attribut pour chaque objet ( obj1 et obj2 ).
Supposons que ma classe Test ait des milliers de ces objets? La création d'un attribut supplémentaire dict
pour chaque objet entraînera beaucoup de surcharge (mémoire, puissance de calcul, etc.) dans mon code.
__slots__
Maintenant, dans l'exemple suivant, ma classe Test contient un __slots__
attribut. Maintenant, je ne peux plus ajouter de nouveaux attributs à mes objets (sauf l'attribut x
) et python ne crée dict
plus d'attribut. 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'
Une autre utilisation quelque peu obscure de __slots__
est d'ajouter des attributs à un proxy d'objet à partir du package ProxyTypes, qui faisait auparavant partie du projet PEAK. Son ObjectWrapper
vous permet de proxy un autre objet, mais d' intercepter toutes les interactions avec l'objet proxy. Il n'est pas très utilisé (et pas de prise en charge de 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 fait rebondir tout accès à l'objet proxy via l'ioloop, en utilisant thread-safeconcurrent.Future
objets pour synchroniser et renvoyer les résultats.
Par défaut, tout accès d'attribut à l'objet proxy vous donnera le résultat de l'objet 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
Vous n'avez - essentiellement - aucune utilité pour __slots__
.
Pour le moment où vous pensez en avoir besoin __slots__
, vous voulez réellement utiliser des modèles de conception légers ou mouches . Ce sont des cas où vous ne souhaitez plus utiliser des objets purement Python. Au lieu de cela, vous voulez un wrapper de type objet Python 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
L'encapsuleur de classe n'a pas d'attributs - il fournit simplement 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 à de simples fonctions fonctionnant sur le tableau de données sous-jacent.
__slots__
?
__slots__
sont deux techniques d'optimisation pour économiser de la mémoire. __slots__
présente des avantages lorsque vous disposez de nombreux objets ainsi que d'un motif de conception Flyweight. Les deux résolvent le même problème.
__slots__
vraiment la réponse, et comme Evgeni le fait remarquer, il peut être ajouté comme une simple réflexion après coup (par exemple, vous pouvez vous concentrer sur l'exactitude, puis ajouter des performances).
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 de meilleures performances lors de l'instanciation de grandes quantités d'objets, ce qui est intéressant, par exemple, lors de l'analyse de gros documents en objets ou à partir d'une base de données.
Voici une comparaison de la création d'arbres d'objets avec un million d'entrées, en utilisant des emplacements et sans emplacements. Comme référence également les performances lors de l'utilisation de dict 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)
code de test, mode détaillé:
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
class Child(BaseA, BaseB): __slots__ = ('a', 'b')
exemple avec les parents empy-slot. Pourquoi est-ildictproxy
créé ici au lieu de lever unAttributeError
pourc
?