2010-06-30 10 views
10

Sono ancora abbastanza nuovo per Python e la mia esperienza OO proviene da Java. Così ho qualche codice che ho scritto in Python che sta agendo molto insolito per me, dato il seguente codice:Scopo ed elenchi di classe Python

class MyClass(): 
     mylist = [] 
     mynum = 0 

     def __init__(self): 
       # populate list with some value. 
       self.mylist.append("Hey!") 
       # increment mynum. 

       self.mynum += 1 

a = MyClass() 

print a.mylist 
print a.mynum 

b = MyClass() 

print b.mylist 
print b.mynum 

L'esecuzione di questo risultato è la seguente output:

['Hey!'] 
1 
['Hey!', 'Hey!'] 
1 

Chiaramente, mi aspetterei le variabili di classe per ottenere gli stessi dati esatti e lo stesso risultato esatto ... Quello che non riesco a trovare da nessuna parte è ciò che rende una lista diversa da una stringa o un numero, perché la lista fa riferimento alla stessa lista da la prima istanziazione in quelli successivi? Chiaramente probabilmente sto fraintendendo un qualche tipo di meccanica dell'ambito o meccanica della creazione di elenchi.

risposta

8

La risposta di tlayton è parte della storia, ma non spiega tutto.

Aggiungi un

print MyClass.mynum 

per diventare ancora più confuso :). Stampa '0'. Perché?Perché la linea

self.mynum += 1 

crea un'istanza variabile e successivamente aumenta. Non aumenta la variabile classe.

La storia della mylist è diversa.

self.mylist.append("Hey!") 

non crea un elenco. Si aspetta una variabile con una funzione 'append' per esistere. Poiché l'istanza non ha tale variabile, finisce con riferimento a quella della classe, che esiste , poiché l'hai inizializzata. Proprio come in Java, un'istanza può "implicitamente" fare riferimento a una variabile di classe. Un avvertimento come "I campi di classe dovrebbero essere referenziati dalla classe, non da un'istanza" (o qualcosa di simile, è passato un po 'di tempo da quando l'ho visto in Java) sarebbe stato utile. Aggiungere una linea

print MyClass.mylist 

per verificare questa risposta :).

In breve: si inizializzano le variabili di classe e si aggiornano le variabili di istanza. Le istanze possono fare riferimento a variabili di classe, ma alcune istruzioni di 'aggiornamento' creeranno automaticamente le variabili di istanza per te.

+0

Manca ancora il punto chiave; vedere la risposta di Ken. 'self.mynum + = 1' si espande in' self.mynum = self.mynum + 1'. Quando viene eseguito, prima viene valutato il lato destro. 'self.mynum' cerca' mynum' sulle istanze e non riesce a trovarlo (almeno la prima volta che viene chiamato '__init__'), quindi cerca (e trova)' mynum' nella classe. Quindi il 'self.mynum' sul lato destro valuta il valore di' MyClass.mynum'. Quel valore è ora assegnato a se stesso.mynum', che qui si riferisce alla variabile di istanza. –

5

Quello che stai facendo qui non è solo la creazione di una variabile di classe. In Python, le variabili definite nel corpo della classe generano sia una variabile di classe ("MyClass.mylist") che una variabile di istanza ("a.mylist"). Queste sono variabili separate, non solo nomi diversi per una singola variabile.

Tuttavia, quando una variabile viene inizializzata in questo modo, il valore iniziale viene valutato solo una volta e passato alle variabili di ogni istanza. Ciò significa che, nel tuo codice, la variabile mylist di ogni istanza di MyClass fa riferimento a un singolo oggetto elenco.

La differenza tra un elenco e un numero in questo caso è che, come in Java, i valori primitivi come i numeri vengono copiati quando passati da una variabile a un'altra. Ciò si traduce nel comportamento che vedi; anche se l'inizializzazione della variabile viene valutata solo una volta, lo 0 viene copiato quando viene passato alla variabile di ciascuna istanza. Come oggetto, però, la lista non fa nulla, quindi le tue chiamate append() provengono tutte dalla stessa lista. Prova invece:

class MyClass(): 
    def __init__(self): 
     self.mylist = ["Hey"] 
     self.mynum = 1 

Ciò causerà la valutazione del valore separatamente ogni volta che viene creata un'istanza. A differenza di Java, non hai bisogno delle dichiarazioni dei body class per accompagnare questo frammento; i compiti in __init __() fungono da tutte le dichiarazioni necessarie.

+1

"Queste sono variabili separate, non solo nomi diversi per una singola variabile." <- Non penso che sia vero. Prova a fare 'id (a.mylist)' e 'id (MyClass.mylist)' e guarda cosa ottieni. –

+0

Sono variabili separate, facendo l'importante distinzione tra variabili e valori. queste due chiamate a id() restituiranno uguale, poiché sia ​​a.mylist che MyClass.mylist si riferiscono allo stesso oggetto (creato dopo la valutazione della riga "mylist = []", ma sono ancora separati, assegnando un elenco completamente diverso a uno di essi in seguito non cambierà il valore dell'altro – tlayton

+0

@Mark: hai ragione che non è vero Le prime due righe della classe creano variabili di classe e solo variabili di classe. Vedi la mia risposta per la storia corretta Tuttavia, il tuo metodo per mostrare che non è vero non funziona: l'istanza può fare riferimento alla variabile di classe e quei due comandi restituiranno di fatto lo stesso id. Tuttavia, quando vengono ripetuti per la variabile 'mynum', essi restituisce ID diversi. – Confusion

5

ritengo la differenza è che += è un'assegnazione (proprio come = e +), mentre append cambia un oggetto sul posto.

mylist = [] 
    mynum = 0 

Assegna alcune variabili di classe, una sola volta, al momento della definizione della classe.

self.mylist.append("Hey!") 

Questo modifica il valore MyClass.mylist aggiungendo una stringa.

self.mynum += 1 

Questo è lo stesso self.mynum = self.mynum + 1, cioè, assegna self.mynum (membro esempio). La lettura da self.mynum ricade sul membro della classe poiché in quel momento non vi è alcun membro di istanza con quel nome.

+0

Tecnicamente, MyClass.mylist non è un valore. Come self.mylist, è una variabile. La chiamata a self.mylist.append() non modifica il valore di MyClass.mylist; altera la lista a cui entrambe le variabili si riferiscono. – tlayton

+0

+1. Bella risposta. @tlayton: cosa intendi quando dici "valore"? Direi sicuramente che chiamare "self.mylist.append (" Ehi! ")" Altera il valore di "MyClass.mylist". –

+0

Non secondo la mia comprensione del significato di "valore" in questo contesto. Il valore di MyClass.mylist è l'elenco a cui fa riferimento. chiamare append() non fa in modo che mylist faccia riferimento a un elenco diverso, ma piuttosto altera la lista stessa. Probabilmente sto interpretando un po 'troppo il "valore" anche dal punto di vista tecnico. – tlayton