Risposte:
In Python, qual è lo scopo
__slots__
e quali casi si dovrebbero evitare?
L'attributo speciale __slots__
ti consente di indicare esplicitamente quali attributi dell'istanza ti aspetti siano le istanze dell'oggetto, con i risultati previsti:
Il risparmio di spazio è di
__dict__
.__dict__
e __weakref__
creazione se le classi padre le negano e tu le dichiari __slots__
.Piccolo avvertimento, dovresti dichiarare una determinata slot una sola volta in un albero ereditario. Per esempio:
class Base:
__slots__ = 'foo', 'bar'
class Right(Base):
__slots__ = 'baz',
class Wrong(Base):
__slots__ = 'foo', 'bar', 'baz' # redundant foo and bar
Python non si oppone quando sbagli (probabilmente dovrebbe), altrimenti i problemi potrebbero non manifestarsi, ma i tuoi oggetti occuperanno più spazio di quanto dovrebbero altrimenti. Python 3.8:
>>> from sys import getsizeof
>>> getsizeof(Right()), getsizeof(Wrong())
(56, 72)
Questo perché il descrittore di slot della Base ha uno slot separato da quello sbagliato. Questo di solito non dovrebbe emergere, ma potrebbe:
>>> 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'
L'avvertenza più grande riguarda l'ereditarietà multipla: non è possibile combinare più "classi parent con slot non vuoti".
Per soddisfare questa restrizione, segui le migliori pratiche: Fattorizza tutte le astrazioni tranne una o tutte i genitori che la loro classe concreta rispettivamente e la tua nuova classe concreta erediteranno collettivamente - dando alle astrazioni slot vuoti (proprio come le classi base astratte nella libreria standard).
Vedi la sezione sull'ereditarietà multipla di seguito per un esempio.
Affinché gli attributi nominati __slots__
siano effettivamente archiviati negli slot anziché in __dict__
, una classe deve ereditare object
.
Per impedire la creazione di a __dict__
, è necessario ereditare object
e tutte le classi nell'ereditarietà devono dichiarare __slots__
e nessuna di esse può avere una '__dict__'
voce.
Ci sono molti dettagli se desideri continuare a leggere.
__slots__
: accesso più rapido agli attributi.Il creatore di Python, Guido van Rossum, afferma di aver effettivamente creato __slots__
per un accesso più rapido agli attributi.
È banale dimostrare un accesso più rapido misurabile in modo significativo:
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
e
>>> min(timeit.repeat(get_set_delete_fn(slotted)))
0.2846834529991611
>>> min(timeit.repeat(get_set_delete_fn(not_slotted)))
0.3664822799983085
L'accesso con slot è quasi il 30% più veloce in Python 3.5 su Ubuntu.
>>> 0.3664822799983085 / 0.2846834529991611
1.2873325658284342
In Python 2 su Windows l'ho misurato circa il 15% più velocemente.
__slots__
: Risparmio di memoriaUn altro scopo di __slots__
è ridurre lo spazio in memoria occupato da ciascuna istanza di oggetto.
Il mio contributo alla documentazione indica chiaramente i motivi alla base di questo :
Lo spazio risparmiato sull'utilizzo
__dict__
può essere significativo.
SQLAlchemy attribuisce molti risparmi di memoria a __slots__
.
Per verificarlo, usando la distribuzione Anaconda di Python 2.7 su Ubuntu Linux, con guppy.hpy
(aka heapy) e sys.getsizeof
, la dimensione di un'istanza di classe senza __slots__
dichiarata, e nient'altro, è di 64 byte. Ciò non include il __dict__
. Grazie Python per la valutazione pigra di nuovo, __dict__
apparentemente non viene chiamato in esistenza fino a quando non viene referenziato, ma le classi senza dati sono di solito inutili. Quando chiamato in esistenza, l' __dict__
attributo è un minimo di 280 byte in più.
Al contrario, un'istanza di classe con __slots__
dichiarata essere()
(nessun dato) è solo 16 byte e 56 byte totali con un elemento negli slot, 64 con due.
Per Python a 64 bit, illustrerò il consumo di memoria in byte in Python 2.7 e 3.6, per __slots__
e __dict__
(nessuno slot definito) per ogni punto in cui il dict cresce in 3.6 (tranne per gli attributi 0, 1 e 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
Quindi, nonostante i piccoli dadi in Python 3, vediamo come __slots__
ridimensionare piacevolmente le istanze per farci risparmiare memoria, e questo è uno dei motivi principali che vorresti usare __slots__
.
Solo per completezza delle mie note, nota che c'è un costo una tantum per slot nello spazio dei nomi della classe di 64 byte in Python 2 e 72 byte in Python 3, perché gli slot usano descrittori di dati come proprietà, chiamati "membri".
>>> Foo.foo
<member 'foo' of 'Foo' objects>
>>> type(Foo.foo)
<class 'member_descriptor'>
>>> getsizeof(Foo.foo)
72
__slots__
:Per negare la creazione di a __dict__
, è necessario eseguire la sottoclasse object
:
class Base(object):
__slots__ = ()
adesso:
>>> 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'
O sottoclasse un'altra classe che definisce __slots__
class Child(Base):
__slots__ = ('a',)
e adesso:
c = Child()
c.a = 'a'
ma:
>>> 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'
Per consentire la __dict__
creazione durante la sottoclasse di oggetti con slot , basta aggiungere '__dict__'
a __slots__
(nota che gli slot sono ordinati e non dovresti ripetere gli slot che sono già nelle classi principali):
class SlottedWithDict(Child):
__slots__ = ('__dict__', 'b')
swd = SlottedWithDict()
swd.a = 'a'
swd.b = 'b'
swd.c = 'c'
e
>>> swd.__dict__
{'c': 'c'}
Oppure non hai nemmeno bisogno di dichiarare __slots__
nella tua sottoclasse e utilizzerai comunque le slot dei genitori, ma non limiterai la creazione di un __dict__
:
class NoSlots(Child): pass
ns = NoSlots()
ns.a = 'a'
ns.b = 'b'
E:
>>> ns.__dict__
{'b': 'b'}
Tuttavia, __slots__
possono causare problemi per l'ereditarietà multipla:
class BaseA(object):
__slots__ = ('a',)
class BaseB(object):
__slots__ = ('b',)
Perché la creazione di una classe figlio dai genitori con entrambi gli slot non vuoti non riesce:
>>> 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
Se ti imbatti in questo problema, potresti semplicemente rimuoverlo __slots__
dai genitori, o se hai il controllo dei genitori, dare loro spazi vuoti o rifattorizzare le astrazioni:
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__'
a __slots__
per ottenere l'assegnazione dinamica:class Foo(object):
__slots__ = 'bar', 'baz', '__dict__'
e adesso:
>>> foo = Foo()
>>> foo.boink = 'boink'
Quindi, con '__dict__'
negli slot perdiamo alcuni dei vantaggi delle dimensioni con il vantaggio di avere un'assegnazione dinamica e avere ancora slot per i nomi che ci aspettiamo.
Quando si eredita da un oggetto che non è inserito, si ottiene lo stesso tipo di semantica quando si usano __slots__
- nomi che fanno __slots__
riferimento a valori di inserimento, mentre qualsiasi altro valore viene inserito nell'istanza __dict__
.
Evitare __slots__
perché vuoi essere in grado di aggiungere attributi al volo non è in realtà un buon motivo - basta aggiungere "__dict__"
al tuo__slots__
se necessario.
È possibile aggiungere in modo simile __weakref__
al __slots__
modo esplicito se avete bisogno di tale funzionalità.
L'integrato namedtuple crea istanze immutabili che sono molto leggere (essenzialmente, la dimensione delle tuple) ma per ottenere i benefici, è necessario farlo da soli se li si esegue la sottoclasse:
from collections import namedtuple
class MyNT(namedtuple('MyNT', 'bar baz')):
"""MyNT is an immutable and lightweight object"""
__slots__ = ()
utilizzo:
>>> nt = MyNT('bar', 'baz')
>>> nt.bar
'bar'
>>> nt.baz
'baz'
E il tentativo di assegnare un attributo imprevisto genera un AttributeError
perché abbiamo impedito la creazione di __dict__
:
>>> nt.quux = 'quux'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyNT' object has no attribute 'quux'
È possibile consentire __dict__
la creazione, lasciando fuori__slots__ = ()
, ma non è possibile utilizzare non vuota __slots__
con sottotipi di tuple.
Anche quando gli slot non vuoti sono gli stessi per più genitori, non possono essere usati insieme:
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'uso di un vuoto __slots__
nel genitore sembra fornire la massima flessibilità, consentendo al bambino di scegliere di impedire o consentire (aggiungendo '__dict__'
per ottenere un compito dinamico, vedere la sezione sopra) la creazione di 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'
Non si hanno di avere slot - quindi se li si aggiunge, e rimuoverli in seguito, non dovrebbe causare problemi.
Andare su un arto qui : se stai componendo mixin o usi classi base astratte , che non sono pensate per essere istanziate, un vuoto __slots__
in quei genitori sembra essere il modo migliore per andare in termini di flessibilità per le sottoclassi.
Per dimostrare, per prima cosa, creiamo una classe con codice che vorremmo usare in eredità multipla
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)})'
Potremmo utilizzare quanto sopra direttamente ereditando e dichiarando gli slot previsti:
class Foo(AbstractBase):
__slots__ = 'a', 'b'
Ma non ci interessa, è una banale eredità singola, abbiamo bisogno di un'altra classe da cui possiamo anche ereditare, forse con un attributo rumoroso:
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
Ora se entrambe le basi avessero slot vuoti, non potremmo fare quanto segue. (In effetti, se volessimo, avremmo potuto dare AbstractBase
slot vuoti aeb, lasciandoli fuori dalla seguente dichiarazione - lasciandoli dentro sarebbe sbagliato):
class Concretion(AbstractBase, AbstractBaseC):
__slots__ = 'a b _c'.split()
E ora abbiamo funzionalità da entrambi tramite ereditarietà multipla e possiamo ancora negare __dict__
e creare __weakref__
istanze:
>>> 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__
compito con un'altra classe che non li ha (e non puoi aggiungerli) a meno che i layout degli slot siano identici. (Sono molto interessato a sapere chi lo sta facendo e perché.)Potresti essere in grado di prendere in giro ulteriori avvertimenti dal resto della __slots__
documentazione ( i documenti di sviluppo 3.7 sono i più attuali) , a cui ho apportato significativi contributi recenti.
Le risposte principali attuali citano informazioni obsolete e sono abbastanza ondulate e mancano il segno in alcuni modi importanti.
__slots__
quando si crea un'istanza di molti oggetti"Quoto:
"Vorresti usare
__slots__
se hai intenzione di creare un'istanza di molti (centinaia, migliaia) oggetti della stessa classe."
Le classi base astratte, ad esempio, dal collections
modulo, non sono istanziate, ma __slots__
sono dichiarate per esse.
Perché?
Se un utente desidera negare __dict__
o __weakref__
creare, tali elementi non devono essere disponibili nelle classi principali.
__slots__
contribuisce alla riusabilità durante la creazione di interfacce o mixin.
È vero che molti utenti di Python non stanno scrivendo per riusabilità, ma quando lo hai, avere la possibilità di negare l'utilizzo dello spazio non necessario è prezioso.
__slots__
non rompe il decapaggioQuando decapaggio di un oggetto scanalato, è possibile che si lamenta di un fuorviante TypeError
:
>>> pickle.loads(pickle.dumps(f))
TypeError: a class that defines __slots__ without defining __getstate__ cannot be pickled
Questo non è corretto. Questo messaggio proviene dal protocollo più vecchio, che è quello predefinito. È possibile selezionare il protocollo più recente con l' -1
argomento. In Python 2.7 questo sarebbe 2
(che è stato introdotto in 2.3), e in 3.6 lo è 4
.
>>> pickle.loads(pickle.dumps(f, -1))
<__main__.Foo object at 0x1129C770>
in Python 2.7:
>>> pickle.loads(pickle.dumps(f, 2))
<__main__.Foo object at 0x1129C770>
in Python 3.6
>>> pickle.loads(pickle.dumps(f, 4))
<__main__.Foo object at 0x1129C770>
Quindi vorrei tenerlo a mente, in quanto si tratta di un problema risolto.
Il primo paragrafo è metà spiegazione breve, metà predittiva. Ecco l'unica parte che risponde effettivamente alla domanda
L'uso corretto di
__slots__
è per risparmiare spazio negli oggetti. Invece di avere un dict dinamico che consente di aggiungere attributi agli oggetti in qualsiasi momento, esiste una struttura statica che non consente aggiunte dopo la creazione. Ciò consente di risparmiare l'overhead di un dict per ogni oggetto che utilizza gli slot
La seconda metà è un pio desiderio, e fuori dal segno:
Sebbene a volte si tratti di un'utile ottimizzazione, sarebbe del tutto superfluo se l'interprete Python fosse abbastanza dinamico da richiedere il comando solo in presenza di aggiunte all'oggetto.
Python in realtà fa qualcosa di simile a questo, creando solo __dict__
quando si accede, ma creare molti oggetti senza dati è abbastanza ridicolo.
Il secondo paragrafo semplifica e manca i motivi reali da evitare __slots__
. Il seguito non è un vero motivo per evitare gli slot (per motivi reali , vedi il resto della mia risposta sopra.):
Cambiano il comportamento degli oggetti che hanno slot in un modo che può essere abusato da maniaci del controllo e weenies di tipizzazione statici.
Quindi prosegue discutendo altri modi per raggiungere quell'obiettivo perverso con Python, senza discutere di nulla a che fare con __slots__
.
Il terzo paragrafo è un pensiero più ambizioso. Insieme è per lo più contenuto off-the-mark che il risponditore non ha nemmeno scritto e contribuisce a munizioni per i critici del sito.
Crea alcuni oggetti normali e oggetti scanalati:
>>> class Foo(object): pass
>>> class Bar(object): __slots__ = ()
Istanzia un milione di loro:
>>> foos = [Foo() for f in xrange(1000000)]
>>> bars = [Bar() for b in xrange(1000000)]
Ispezionare con 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
...
Accedi agli oggetti normali e ai loro __dict__
e ispeziona di nuovo:
>>> 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
...
Ciò è coerente con la storia di Python, dai tipi e dalle classi Unifying in Python 2.2
Se si esegue la sottoclasse di un tipo incorporato, lo spazio aggiuntivo viene automaticamente aggiunto alle istanze per accogliere
__dict__
e__weakrefs__
. (Non__dict__
viene inizializzato fino a quando non lo si utilizza, quindi non dovresti preoccuparti dello spazio occupato da un dizionario vuoto per ogni istanza che crei.) Se non hai bisogno di questo spazio extra, puoi aggiungere la frase "__slots__ = []
" a la tua classe.
__slots__
. Sul serio! Grazie!
__slots__
circa un anno fa: github.com/python/cpython/pull/1819/files
Citando Jacob Hallen :
L'uso corretto di
__slots__
è per risparmiare spazio negli oggetti. Invece di avere un dict dinamico che consente di aggiungere attributi agli oggetti in qualsiasi momento, esiste una struttura statica che non consente aggiunte dopo la creazione. [Questo uso di__slots__
elimina il sovraccarico di un dict per ogni oggetto.] Sebbene a volte si tratti di un'utile ottimizzazione, sarebbe del tutto inutile se l'interprete di Python fosse abbastanza dinamico in modo da richiedere il dict solo quando c'erano effettivamente aggiunte al oggetto.Sfortunatamente c'è un effetto collaterale negli slot. Cambiano il comportamento degli oggetti che hanno slot in un modo che può essere abusato da maniaci del controllo e weenies di tipizzazione statici. Questo è negativo, perché i maniaci del controllo dovrebbero abusare delle metaclasse e i weenies di tipizzazione statici dovrebbero abusare dei decoratori, poiché in Python dovrebbe esserci solo un modo ovvio di fare qualcosa.
Rendere CPython abbastanza intelligente da gestire il risparmio di spazio senza
__slots__
è una grande impresa, motivo per cui probabilmente non è nell'elenco delle modifiche per P3k (ancora).
__slots__
non affronta gli stessi problemi della digitazione statica. Ad esempio, in C ++, non si sta limitando la dichiarazione di una variabile membro, è l'assegnazione di un tipo non intenzionale (e il compilatore applicato) a quella variabile. Non sto perdonando l'uso di __slots__
, solo interessato alla conversazione. Grazie!
Dovresti usarlo __slots__
se hai intenzione di creare un'istanza di molti (centinaia, migliaia) oggetti della stessa classe. __slots__
esiste solo come strumento di ottimizzazione della memoria.
È altamente sconsigliato utilizzare __slots__
per limitare la creazione di attributi.
Gli oggetti di decapaggio con __slots__
non funzioneranno con il protocollo di decapaggio predefinito (più vecchio); è necessario specificare una versione successiva.
Alcune altre funzionalità di introspezione di Python possono anche essere influenzate negativamente.
Ogni oggetto python ha un __dict__
attributo che è un dizionario che contiene tutti gli altri attributi. ad es. quando si digita self.attr
python sta effettivamente facendo self.__dict__['attr']
. Come puoi immaginare, l'utilizzo di un dizionario per memorizzare l'attributo richiede dello spazio e del tempo extra per accedervi.
Tuttavia, quando si utilizza __slots__
, qualsiasi oggetto creato per quella classe non avrà un __dict__
attributo. Al contrario, l'accesso a tutti gli attributi avviene direttamente tramite puntatori.
Quindi, se si desidera una struttura in stile C piuttosto che una classe completa, è possibile utilizzare __slots__
per compattare le dimensioni degli oggetti e ridurre il tempo di accesso agli attributi. Un buon esempio è una classe Point che contiene gli attributi x & y. Se hai molti punti, puoi provare a usare __slots__
per conservare un po 'di memoria.
__slots__
definita non è come una struttura in stile C. Esiste un dizionario a livello di classe che associa i nomi degli attributi agli indici, altrimenti non sarebbe possibile quanto segue: class A(object): __slots__= "value",\n\na=A(); setattr(a, 'value', 1)
Penso davvero che questa risposta debba essere chiarita (posso farlo se vuoi). Inoltre, non sono sicuro che instance.__hidden_attributes[instance.__class__[attrname]]
sia più veloce di instance.__dict__[attrname]
.
Oltre alle altre risposte, ecco un esempio di utilizzo __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']
Quindi, per implementare __slots__
, ci vuole solo una linea in più (e rendere la tua classe una nuova classe se non lo è già). In questo modo è possibile ridurre il footprint di memoria di quelle classi di 5 volte , a spese di dover scrivere un codice di decapaggio personalizzato, se e quando diventa necessario.
Gli slot sono molto utili per le chiamate in libreria per eliminare l '"invio del metodo denominato" quando si effettuano chiamate di funzione. Questo è menzionato nella documentazione SWIG . Per le librerie ad alte prestazioni che vogliono ridurre il sovraccarico di funzioni per le funzioni comunemente chiamate utilizzando gli slot è molto più veloce.
Ora questo potrebbe non essere direttamente correlato alla domanda dei PO. È più legato alla costruzione di estensioni che all'utilizzo della sintassi degli slot su un oggetto. Ma aiuta a completare il quadro per l'uso di slot e alcuni dei ragionamenti dietro di loro.
Un attributo di un'istanza di classe ha 3 proprietà: l'istanza, il nome dell'attributo e il valore dell'attributo.
Nel normale accesso agli attributi , l'istanza funge da dizionario e il nome dell'attributo funge da chiave in quel dizionario che cerca valore.
istanza (attributo) -> valore
In accesso __slots__ , il nome dell'attributo agisce come il dizionario e l'istanza si comporta come la chiave nel dizionario guardando in alto il valore.
attributo (istanza) -> valore
Nel modello flyweight , il nome dell'attributo funge da dizionario e il valore funge da chiave nel dizionario che cerca l'istanza.
attributo (valore) -> istanza
__slots__
?
Un esempio molto semplice di __slot__
attributo.
__slots__
Se non ho un __slot__
attributo nella mia classe, posso aggiungere nuovi attributi ai miei oggetti.
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}
Se si guarda a esempio di cui sopra, si può vedere che obj1 e obj2 hanno un proprio x e y attributi e pitone ha anche creato un dict
attributo per ogni oggetto ( obj1 e obj2 ).
Supponiamo che il mio test di classe abbia migliaia di tali oggetti? La creazione di un attributo aggiuntivo dict
per ogni oggetto causerà un sacco di sovraccarico (memoria, potenza di calcolo ecc.) Nel mio codice.
__slots__
Ora nell'esempio seguente la mia classe Test contiene l' __slots__
attributo. Ora non posso aggiungere nuovi attributi ai miei oggetti (tranne l'attributo x
) e python non crea più un dict
attributo. Questo elimina il sovraccarico per ogni oggetto, che può diventare significativo se si hanno molti oggetti.
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'
Un altro uso un po 'oscuro di __slots__
è aggiungere attributi a un proxy oggetto dal pacchetto ProxyTypes, precedentemente parte del progetto PEAK. La sua ObjectWrapper
si permette di procura un altro oggetto, ma intercetta tutte le interazioni con l'dell'oggetto proxy. Non è molto comunemente usato (e non supporta Python 3), ma l'abbiamo usato per implementare un wrapper di blocco thread-safe attorno a un'implementazione asincrona basata su tornado che rimbalza tutti gli accessi all'oggetto proxy attraverso l'ioloop, usando thread-safe concurrent.Future
oggetti per sincronizzare e restituire risultati.
Per impostazione predefinita, qualsiasi accesso all'attributo all'oggetto proxy ti darà il risultato dall'oggetto proxy. Se è necessario aggiungere un attributo sull'oggetto proxy, __slots__
può essere utilizzato.
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
In sostanza, non serve a nulla __slots__
.
Per il momento in cui si pensa che potrebbe essere necessario __slots__
, in realtà si desidera utilizzare leggeri o peso mosca modelli di progettazione. Questi sono casi in cui non si desidera più utilizzare oggetti puramente Python. Invece, vuoi un wrapper simile ad un oggetto Python attorno ad un array, struct o numpy array.
class Flyweight(object):
def get(self, theData, index):
return theData[index]
def set(self, theData, index, value):
theData[index]= value
Il wrapper di classe non ha attributi: fornisce solo metodi che agiscono sui dati sottostanti. I metodi possono essere ridotti ai metodi di classe. In effetti, potrebbe essere ridotto a sole funzioni che operano sull'array di dati sottostante.
__slots__
?
__slots__
sono entrambe tecniche di ottimizzazione per risparmiare memoria. __slots__
mostra i vantaggi quando si hanno molti molti oggetti e il modello di progettazione Flyweight. Entrambi risolvono lo stesso problema.
__slots__
è davvero la risposta, e come sottolinea Evgeni, può essere aggiunto come un semplice ripensamento (ad esempio, puoi prima concentrarti sulla correttezza, quindi aggiungere prestazioni).
La domanda originale riguardava casi d'uso generali non solo sulla memoria. Quindi, si dovrebbe menzionare qui che si ottengono anche prestazioni migliori quando si crea un'istanza di grandi quantità di oggetti - interessante ad esempio quando si analizzano documenti di grandi dimensioni in oggetti o da un database.
Ecco un confronto tra la creazione di alberi di oggetti con un milione di voci, l'utilizzo di slot e senza slot. Come riferimento anche le prestazioni quando si usano dicts semplici per gli alberi (Py2.7.10 su 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
Classi di prova (ident, appart da slot):
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, modalità dettagliata:
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')
esempio con i genitori-empy-slot. Perché qui vienedictproxy
creato invece di sollevare unAttributeError
forc
?