2011-09-25 14 views
6

Sono nuovo in Python e ho imparato che gli attributi di classe sono come membri di dati statici in C++. Tuttavia, mi sono confuso dopo aver provato il seguente codice:python: attributi di classe e attributi di istanza

>>> class Foo: 
...  a=1 
... 
>>> f1=Foo(); 
>>> f2=Foo() 
>>> f1.a 
1 
>>> f1.a=5 
>>> f1.a 
5 
>>> f2.a 
1 

Non dovrebbe anche f2.a uguale a 5? Se un è definito come una lista invece di un numero intero, si prevede

il comportamento:

>>> class Foo: 
...  a=[] 
... 
>>> f1=Foo(); 
>>> f2=Foo() 
>>> f1.a 
[] 
>>> f1.a.append(5) 
>>> f1.a 
[5] 
>>> f2.a 
[5] 

ho guardato Python: Difference between class and instance attributes, ma non risponde alla mia domanda.

Qualcuno può spiegare perché la differenza? Grazie

+0

possibile duplicato di [Come evitare di avere dati di classe Python condivisi tra le istanze?] (Http: // stackoverflow.it/questions/1680528/how-do-i-avoid-having-python-class-data-shared-instance) –

risposta

7

Non stai facendo la stessa cosa nel tuo secondo esempio. In voi primo esempio, si sta assegnando f1.a un nuovo valore:

f1.a = 5 

Nel secondo esempio, si sta semplicemente estendendo un elenco:

f1.a.append(5) 

questo non cambia quello che f1.a sta indicando. Se si dovesse invece di fare questo:

f1.a = [5] 

Si potrebbe scoprire che questo comporta lo stesso come il primo esempio.

Ma considera questo esempio:

>>> f1=Foo() 
>>> f2=Foo() 
>>> Foo.a = 5 
>>> f1.a 
5 
>>> f2.a 
5 

In questo esempio, si sta effettivamente cambiando il valore dell'attributoclasse, e il cambiamento è visibile in tutte le istanze della classe.Quando si Tipo:

f1.a = 5 

Stai sovrascrivendo l'attributo class con un'istanza attributi .

+2

"Stai sovrascrivendo l'attributo di classe con un attributo di istanza." Io non la penso così L'attributo della classe rimane intatto. Nel codice dell'utente 11869, l'attributo ** a ** è CREATO nel namspace di ** f1 ** con l'istruzione '' f1.a = 5'', perché questo attributo non esisteva prima, poiché è mostrato nella mia risposta usando ** \ _ \ _ dict __ ** – eyquem

0

Quello che succede è la seguente:

Quando si crea un'istanza di un nuovo oggetto f1 = Foo(), non ha nessun attributi del proprio. Ogni volta che si tenta di accedere ad esempio f1.a, si ottiene reindirizzati alla classe Foo.a:

print f1.__dict__ 
{} 

print f1.a 
1 

Tuttavia, se si impostaf1.a = 5, l'istanza ottiene un nuovo attributo di tale valore:

print f1.__dict__ 
{'a': 5} 

La definizione della classe non è toccata da questo, come lo sono altre istanze.

Nel secondo esempio, non si riassegna nulla. Con append stai usando solo la stessa lista che è stata definita nella classe. Pertanto, l'istanza fa ancora riferimento a tale elenco, così come tutte le altre istanze.

+1

Uguale alla risposta che stavo per digitare. Vorrei solo aggiungere che la ragione per cui "funziona" con una lista è perché in quel caso l'oggetto è [mutabile] (http://docs.python.org/library/stdtypes.html#typesseq-mutable). Poiché tutte le istanze si riferiscono allo stesso oggetto, le modifiche a quell'oggetto sono viste da tutti. –

+0

@Debilski: le nuove istanze in realtà hanno spesso degli attributi propri (il 'f1 .__ dict__' che scrivi nella tua risposta è uno di questi, come si può vedere confrontare 'id (Foo.a)' e 'id (f1 .a) '). – EOL

5
>>> class Foo: 
...  a=1 
... 
>>> f1=Foo() 
>>> f2=Foo() 
>>> f1.a # no instance attribute here in f1, so look up class attribute in Foo 
1 
>>> f1.a=5 # create new instance attribute in f1 
>>> f1.a # instance attribute here. Great, let's use this. 
5 
>>> f2.a # no instance attribute in f2, look up class attribute in Foo 
1 
>>> 
>>> class Foo: 
...  a=[] 
... 
>>> f1=Foo() 
>>> f2=Foo() 
>>> f1.a   # no instance attribute - look up class attribute in Foo 
[] 
>>> f1.a.append(5) # no instance attribute, modify class attribute in-place 
>>> f1.a   # no instance attribute - look up class attribute in Foo 
[5] 
>>> f2.a   # same here 
[5] 
0

Quando si assegna un attributo in python, non importa dove tale attributo potrebbe essere già definito, il nuovo compito viene sempre applicato all'oggetto assegnato a. Quando dici

>>> f1.a=5 

L'oggetto che ha l'attributo qui è l'istanza, quindi è l'istanza che ottiene il nuovo valore.

3

Il mio consiglio: per capire questi casi, effettuare test utilizzando id, e __dict__ troppo:

class Foo: 
    a=1 

# Foo.a == 1 
# id(Foo.a) == 10021840 
# Foo.__dict__ == {'a': 1, '__module__': '__main__', '__doc__': None} 


f1 = Foo() 

# f1.a == 1 
# id(f1.a) == 10021840 
# f1.__dict__ == {} 

f2 = Foo() 

# f2.a == 1 
# id(f2.a) == 10021840 
# f2.__dict__ == {} 

f1.a = 5 

# f1.a == 5 
# id(f1.a) == 10021792 
# f1.__dict__ == {'a': 5} 

# f2.a == 1 
# id(f2.a) == 10021840 
# f2.__dict__ == {} 

Questo dimostra che fino a quando non è stato eseguito l'istruzione f1.a = 5, l'istanza f1 non ha un attributo personale a.

Quindi, perché l'istruzione print f1.a eseguita prima dello f1.a = 5 produce 1?
Questo perché:

un'istanza di classe ha uno spazio dei nomi implementato come un dizionario che è il primo luogo in cui attributo riferimenti vengono ricercati. Quando un attributo non viene trovato lì e la classe dell'istanza ha un attributo con quel nome, la ricerca continua con gli attributi della classe .

http://docs.python.org/reference/datamodel.html#the-standard-type-hierarchy

9

Python di attributi di classe e gli attributi degli oggetti vengono memorizzati in separato dictionaries. Per l'oggetto f1, è possibile accedervi tramite, rispettivamente, f1.__class__.__dict__ e f1.__dict__. L'esecuzione di print f1.__class__ is Foo emetterà True.

Quando si fa riferimento all'attributo di un oggetto, Python prova prima a cercarlo nel dizionario degli oggetti. Se non lo trova lì, controlla il dizionario della classe (e così via l'ereditarietà dell'ereditarietà).

Quando si assegna a f1.a, si aggiunge una voce al dizionario oggetti per f1. Successive ricerche di f1.a troveranno quella voce. Le ricerche di f2.a troveranno comunque l'attributo class: la voce nel dizionario degli attributi della classe.

Si può causare il valore di f1.a per tornare alla 1 eliminandolo:

del f1.a 

Ciò eliminerà la voce per a nel dizionario oggetti di f1, e le ricerche successive continuerà al dizionario di classe . Quindi, in seguito, print f1.a emetterà 1.

+0

Penso che questa risposta sia più elaborata – Yuncy

+0

@Yuncy Penso che lo stesso – eyquem

Problemi correlati