Domanda

Nella sezione di riferimento del modello di dati Python su slot c'è un elenco di note sull'uso di __slots__. Sono completamente confuso dal primo e dal sesto punto, perché sembrano contraddirsi a vicenda.

Primo elemento:

  • Quando si eredita da una classe senza __dict__, l'attributo <=> di quella classe sarà sempre accessibile, quindi un <=> la definizione nella sottoclasse è privo di significato.

Sesto elemento:

  • L'azione di un <=> la dichiarazione è limitata alla classe dove è definito. Di conseguenza, le sottoclassi avranno un <=> a meno che non definiscano anche <=> (che deve contenere solo nomi di nessuno slot aggiuntivi).

Mi sembra che questi articoli possano essere formulati meglio o mostrati attraverso il codice, ma ho cercato di avvolgere la mia testa e sto ancora diventando confuso. Capisco come <=> dovrebbero essere utilizzati e sto cercando di ottenere un meglio capire come funzionano.

La domanda:

Qualcuno può spiegarmi in parole semplici quali sono le condizioni per l'ereditarietà delle slot durante la sottoclasse?

(Semplici esempi di codice sarebbero utili ma non necessari.)

È stato utile?

Soluzione

Come altri hanno già detto, l'unico motivo per definire __slots__ è quello di risparmiare un po 'di memoria, quando si hanno oggetti semplici con un set predefinito di attributi e non si desidera che ognuno di essi porti con sé un dizionario. Ciò è significativo solo per le classi di cui prevedi di avere molti casi, ovviamente.

I risparmi potrebbero non essere immediatamente ovvi - considera ...:

>>> class NoSlots(object): pass
... 
>>> n = NoSlots()
>>> class WithSlots(object): __slots__ = 'a', 'b', 'c'
... 
>>> w = WithSlots()
>>> n.a = n.b = n.c = 23
>>> w.a = w.b = w.c = 23
>>> sys.getsizeof(n)
32
>>> sys.getsizeof(w)
36

Da questo, sembrerebbe che la dimensione con slot sia più grande rispetto alla dimensione senza slot! Ma questo è un errore, perché sys.getsizeof non considera & Quot; contenuto dell'oggetto & Quot; come il dizionario:

>>> sys.getsizeof(n.__dict__)
140

Poiché il solo dict richiede 140 byte, chiaramente il " 32 byte " si presume che l'oggetto n non consideri tutto ciò che è coinvolto in ciascuna istanza. Puoi fare un lavoro migliore con estensioni di terze parti come pympler :

>>> import pympler.asizeof
>>> pympler.asizeof.asizeof(w)
96
>>> pympler.asizeof.asizeof(n)
288

Questo mostra molto più chiaramente il footprint di memoria che viene salvato da a: per un oggetto semplice come questo caso, è un po 'meno di 200 byte, quasi i 2/3 del footprint complessivo dell'oggetto. Ora, dato che oggigiorno un megabyte in più o in meno non ha molta importanza per la maggior parte delle applicazioni, questo ti dice anche che AB non vale la pena preoccuparti se avrai qualche migliaio di istanze in giro in un tempo - tuttavia, per milioni di casi, fa sicuramente una differenza molto importante. Puoi anche ottenere una microscopica accelerazione (in parte a causa di un migliore utilizzo della cache per piccoli oggetti con b):

$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x'
10000000 loops, best of 3: 0.37 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x'
1000000 loops, best of 3: 0.604 usec per loop
$ python -mtimeit -s'class S(object): __slots__="x","y"' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.28 usec per loop
$ python -mtimeit -s'class S(object): pass' -s's=S(); s.x=s.y=23' 's.x=45'
1000000 loops, best of 3: 0.332 usec per loop

ma questo dipende in qualche modo dalla versione di Python (questi sono i numeri che misuro ripetutamente con 2.5; con 2.6, vedo un vantaggio relativo maggiore rispetto a A per l'impostazione un attributo, ma nessuno a tutto, anzi un piccolo vantaggio dis , per ottenerlo .

Ora, per quanto riguarda l'ereditarietà: affinché un'istanza sia senza dict, tutte le classi nella sua catena di ereditarietà devono avere anche istanze senza dict. Le classi con istanze senza dict sono quelle che definiscono <=>, più la maggior parte dei tipi predefiniti (i tipi predefiniti le cui istanze hanno dicts sono quelli sulle cui istanze è possibile impostare attributi arbitrari, come le funzioni). Le sovrapposizioni nei nomi degli slot non sono vietate, ma sono inutili e sprecano un po 'di memoria, poiché gli slot sono ereditati:

>>> class A(object): __slots__='a'
... 
>>> class AB(A): __slots__='b'
... 
>>> ab=AB()
>>> ab.a = ab.b = 23
>>> 

come vedi, puoi impostare l'attributo <=> su una <=> istanza - <=> stesso definisce solo lo slot <=>, ma eredita lo slot <=> da <=>. Non è vietato ripetere lo slot ereditato:

>>> class ABRed(A): __slots__='a','b'
... 
>>> abr=ABRed()
>>> abr.a = abr.b = 23

ma spreca un po 'di memoria:

>>> pympler.asizeof.asizeof(ab)
88
>>> pympler.asizeof.asizeof(abr)
96

quindi non c'è davvero alcun motivo per farlo.

Altri suggerimenti

class WithSlots(object):
    __slots__ = "a_slot"

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

Primo elemento

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

Sesto elemento

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

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

Probabilmente non dovrai usare __slots__ nel prossimo futuro. Ha solo lo scopo di risparmiare memoria a scapito di una certa flessibilità. A meno che tu non abbia decine di migliaia di oggetti, non importa.

  

Python: come funziona l'ereditarietà di __slots__ nelle sottoclassi?

     

Sono completamente confuso dal primo e dal sesto elemento, perché sembrano contraddirsi a vicenda.

Questi oggetti non si contraddicono a vicenda. Il primo riguarda le sottoclassi di classi che non implementano __dict__, il secondo riguarda le sottoclassi di classi che fanno implementano Bar.

Sottoclassi di classi che non implementano __weakref__

Sono sempre più consapevole del fatto che per quanto i documenti di Python siano (giustamente) famosi, non sono perfetti, specialmente per quanto riguarda le funzionalità meno utilizzate del linguaggio. Modificherei i documenti come segue:

  

Quando si eredita da una classe senza <=>, l'attributo <=>   di quella classe sarà sempre accessibile , quindi una <=> definizione in   la sottoclasse non ha senso .

<=> è ancora significativo per tale classe. Documenta i nomi previsti degli attributi della classe. Inoltre crea slot per quegli attributi: otterranno ricerche più veloci e utilizzeranno meno spazio. Permette solo altri attributi, che verranno assegnati a <=>.

Questa è stata accettata ed è ora nella ultima documentazione .

Ecco un esempio:

class Foo: 
    """instances have __dict__"""

class Bar(Foo):
    __slots__ = 'foo', 'bar'

<=> non ha solo gli slot che dichiara, ma ha anche gli slot di Foo - che includono <=>:

>>> b = Bar()
>>> b.foo = 'foo'
>>> b.quux = 'quux'
>>> vars(b)
{'quux': 'quux'}
>>> b.foo
'foo'

Sottoclassi di classi che eseguono implementano <=>

  

L'azione di una dichiarazione <=> è limitata alla classe in cui è presente   è definito. Di conseguenza, le sottoclassi avranno <=> a meno che non lo siano   definire anche <=> (che deve contenere solo nomi di qualsiasi ulteriore   slot).

Beh, non è neanche del tutto giusto. L'azione di una <=> dichiarazione è non interamente limitata alla classe in cui è definita. Possono avere implicazioni per l'ereditarietà multipla, ad esempio.

Lo cambierei in:

  

Per le classi in un albero ereditario che definisce <=>, le sottoclassi avranno un <=> a meno che non   definire anche <=> (che deve contenere solo nomi di qualsiasi ulteriore   slot).

L'ho effettivamente aggiornato per leggere:

  

L'azione di una <=> dichiarazione non è limitata alla classe   dove è definito. <=> dichiarati in genitori sono disponibili in   classi per bambini. Tuttavia, le sottoclassi secondarie riceveranno un <=> e   <=> a meno che non definiscano anche <=> (che dovrebbe contenere solo nomi di eventuali slot aggiuntivi).

Ecco un esempio:

class Foo:
    __slots__ = 'foo'

class Bar(Foo):
    """instances get __dict__ and __weakref__"""

E vediamo che una sottoclasse di una classe con slot usa gli slot:

>>> b = Bar()
>>> b.foo = 'foo'
>>> b.bar = 'bar'
>>> vars(b)
{'bar': 'bar'}
>>> b.foo
'foo'

(Per ulteriori informazioni su <=>, vedi la mia risposta qui .)

Dalla risposta che hai collegato:

  

L'uso corretto di __slots__ è per risparmiare spazio negli oggetti. Invece di avere un dict dinamico ...

" Quando si eredita da una classe senza __dict__, l'attributo <=> di quella classe sarà sempre accessibile " ;, quindi l'aggiunta del proprio <=> non può impedire agli oggetti di avere un < => e impossibile salvare spazio.

Il bit sul <=> non essere ereditato è un po 'ottuso. Ricorda che è un attributo magico e non si comporta come gli altri attributi, quindi rileggilo dicendo che questo comportamento di slot magici non è ereditato. (Questo è davvero tutto ciò che c'è da fare.)

La mia comprensione è la seguente:

  • classe X non ha __dict__ <-------> classe __slots__ e tutte le sue superclassi hanno <=> specificato

  • in questo caso, gli slot effettivi della classe sono costituiti dall'unione di <=> dichiarazioni per <=> e le sue superclassi; il comportamento non è definito (e diventerà un errore) se questa unione non è disgiunta

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top