Variabili di istanza e variabili di classe in Python


121

Ho classi Python, di cui ho bisogno solo di un'istanza in fase di esecuzione, quindi sarebbe sufficiente avere gli attributi solo una volta per classe e non per istanza. Se ci fosse più di un'istanza (cosa che non accadrà), tutte le istanze dovrebbero avere la stessa configurazione. Mi chiedo quale delle seguenti opzioni sarebbe Python migliore o più "idiomatica".

Variabili di classe:

class MyController(Controller):

  path = "something/"
  children = [AController, BController]

  def action(self, request):
    pass

Variabili di istanza:

class MyController(Controller):

  def __init__(self):
    self.path = "something/"
    self.children = [AController, BController]

  def action(self, request):
    pass

4
Dopo aver letto questa domanda e aver visto la risposta, una delle mie prime domande è stata: "Allora come accedo alle variabili di classe?" - questo perché fino a questo punto ho utilizzato solo variabili di istanza. In risposta alla mia domanda, lo fai tramite il nome della classe stessa, anche se tecnicamente puoi farlo anche tramite un'istanza. Ecco un link da leggere per chiunque abbia la stessa domanda: stackoverflow.com/a/3434596/4561887
Gabriel Staples

Risposte:


159

Se hai comunque una sola istanza, è meglio creare tutte le variabili per istanza, semplicemente perché vi si accederà (un po ') più velocemente (un livello in meno di "ricerca" a causa dell' "ereditarietà" da classe a istanza), e non ci sono aspetti negativi da pesare contro questo piccolo vantaggio.


7
Mai sentito parlare del modello Borg? Avere solo un'istanza era il modo sbagliato di averlo in primo luogo.
Devin Jeanpierre

435
@ Devin, sì, ho sentito parlare del pattern Borg, da quando l'ho introdotto io (nel 2001, cfr code.activestate.com/recipes/… ;-). Ma non c'è niente di sbagliato, in casi semplici, semplicemente con una singola istanza senza applicazione.
Alex Martelli

2
@ user1767754, è facile crearli da soli python -mtimeit, ma dopo averlo appena fatto in python3.4 noto che l'accesso a una intvariabile di classe è in realtà da 5 a 11 nanosecondi più veloce della stessa variabile di istanza sulla mia vecchia workstation - non so cosa codepath lo rende così.
Alex Martelli

45

Facendo eco ai consigli di Mike e Alex e aggiungendo il mio colore ...

L'uso degli attributi di istanza è il tipico ... il più idiomatico Python. Gli attributi di classe non vengono utilizzati molto, poiché i loro casi d'uso sono specifici. Lo stesso vale per i metodi statici e di classe rispetto ai metodi "normali". Sono costrutti speciali che affrontano casi d'uso specifici, altrimenti è codice creato da un programmatore aberrante che vuole mostrare di conoscere qualche angolo oscuro della programmazione Python.

Alex menziona nella sua risposta che l'accesso sarà (un po ') più veloce a causa di un livello di ricerca in meno ... lasciatemi chiarire ulteriormente per coloro che non sanno ancora come funziona. È molto simile all'accesso alle variabili, il cui ordine di ricerca è:

  1. gente del posto
  2. nonlocals
  3. globali
  4. built-in

Per l'accesso agli attributi, l'ordine è:

  1. esempio
  2. classe
  3. classi di base come determinato dall'MRO (ordine di risoluzione del metodo)

Entrambe le tecniche funzionano in modo "rovesciato", il che significa che gli oggetti più locali vengono controllati prima, quindi gli strati esterni vengono controllati in successione.

Nel tuo esempio sopra, diciamo che stai cercando l' pathattributo. Quando incontra un riferimento come " self.path", Python cerca prima gli attributi dell'istanza per una corrispondenza. Quando fallisce, controlla la classe da cui è stata creata l'istanza dell'oggetto. Infine, cercherà le classi di base. Come ha affermato Alex, se il tuo attributo si trova nell'istanza, non ha bisogno di cercare altrove, da qui il tuo piccolo risparmio di tempo.

Tuttavia, se insisti sugli attributi di classe, hai bisogno di quella ricerca extra. Oppure , l'altra alternativa è fare riferimento all'oggetto tramite la classe anziché l'istanza, ad esempio MyController.pathinvece di self.path. Questa è una ricerca diretta che aggira la ricerca differita, ma come alex menziona di seguito ,èuna variabile globale, quindi perdi quel bit che pensavi di salvare (a meno che tu non crei un riferimento locale al nome della classe [globale] ).

La conclusione è che dovresti usare gli attributi di istanza la maggior parte del tempo. Tuttavia, ci saranno occasioni in cui un attributo di classe è lo strumento giusto per il lavoro. Il codice che utilizza entrambi allo stesso tempo richiederà la massima diligenza, perché l'utilizzo selfti darà solo l'oggetto dell'attributo di istanza e l' accesso alle ombre all'attributo di classe con lo stesso nome. In questo caso, è necessario utilizzare l'accesso all'attributo dal nome della classe per fare riferimento ad esso.


@wescpy, ma MyControllerviene cercato nelle globali, quindi il costo totale è superiore a self.pathdove pathè una variabile di istanza (poiché selfè locale per il metodo == ricerca super veloce).
Alex Martelli

ah, vero. buona pesca. credo che l'unica soluzione sia creare un riferimento locale ... a questo punto, non ne vale davvero la pena.
wescpy

24

In caso di dubbio, probabilmente desideri un attributo di istanza.

Gli attributi di classe sono riservati al meglio per casi speciali in cui hanno senso. L'unico caso d'uso molto comune sono i metodi. Non è raro utilizzare attributi di classe per costanti di sola lettura che le istanze devono conoscere (sebbene l'unico vantaggio di questo sia se si desidera l'accesso anche dall'esterno della classe), ma dovresti certamente essere cauto nel memorizzare qualsiasi stato in esse , che raramente è quello che vuoi. Anche se avrai solo un'istanza, dovresti scrivere la classe come faresti con qualsiasi altra, il che di solito significa usare attributi di istanza.


1
Le variabili di classe sono una sorta di costanti di sola lettura. Se Python mi permettesse di definire le costanti, lo avrei scritto come costanti.
deamon

1
@deamon, è leggermente più probabile che metta le mie costanti completamente al di fuori delle definizioni di una classe e le denomini in maiuscolo. Anche metterli in classe va bene. La creazione di attributi di istanza non danneggerà nulla, ma potrebbe essere un po 'strano. Non credo che questo sia un problema in cui la comunità si sottrae troppo a una delle opzioni.
Mike Graham

@MikeGraham FWIW, la Python Style Guide di Google suggerisce di evitare le variabili globali a favore delle variabili di classe. Ci sono però delle eccezioni.
Dennis

Ecco un nuovo collegamento alla Guida allo stile di Python di Google . Ora c'è scritto semplicemente: avoid global variablese la loro definizione è che le variabili globali sono anche variabili dichiarate come attributi di classe. Tuttavia, la guida allo stile di Python ( PEP-8 ) dovrebbe essere il primo posto dove andare per domande di questo tipo. Quindi la tua mente dovrebbe essere lo strumento di scelta (ovviamente puoi anche ottenere idee da Google, ad esempio).
colidyre

4

Stessa domanda su Performance of accessing class variables in Python - il codice qui adattato da @Edward Loper

Le variabili locali sono le più veloci da accedere, più o meno legate alle variabili del modulo, seguite dalle variabili di classe, seguite dalle variabili di istanza.

Sono disponibili 4 ambiti da cui è possibile accedere alle variabili:

  1. Variabili di istanza (self.varname)
  2. Variabili di classe (Classname.varname)
  3. Variabili del modulo (VARNAME)
  4. Variabili locali (varname)

Il test:

import timeit

setup='''
XGLOBAL= 5
class A:
    xclass = 5
    def __init__(self):
        self.xinstance = 5
    def f1(self):
        xlocal = 5
        x = self.xinstance
    def f2(self):
        xlocal = 5
        x = A.xclass
    def f3(self):
        xlocal = 5
        x = XGLOBAL
    def f4(self):
        xlocal = 5
        x = xlocal
a = A()
'''
print('access via instance variable: %.3f' % timeit.timeit('a.f1()', setup=setup, number=300000000) )
print('access via class variable: %.3f' % timeit.timeit('a.f2()', setup=setup, number=300000000) )
print('access via module variable: %.3f' % timeit.timeit('a.f3()', setup=setup, number=300000000) )
print('access via local variable: %.3f' % timeit.timeit('a.f4()', setup=setup, number=300000000) )

Il risultato:

access via instance variable: 93.456
access via class variable: 82.169
access via module variable: 72.634
access via local variable: 72.199
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.