Qual è la differenza tra gli attributi di classe e di istanza?
-
03-07-2019 - |
Domanda
Esiste una distinzione significativa tra:
class A(object):
foo = 5 # some default value
vs.
class B(object):
def __init__(self, foo=5):
self.foo = foo
Se stai creando molte istanze, c'è qualche differenza nelle prestazioni o nei requisiti di spazio per i due stili? Quando leggi il codice, ritieni che il significato dei due stili sia significativamente diverso?
Soluzione
Oltre alle considerazioni sulle prestazioni, esiste una differenza significativa semantica . Nel caso dell'attributo class, c'è solo un oggetto a cui si fa riferimento. Nell'istanza-attributo-impostato-all'istanziazione, possono essere indicati più oggetti. Ad esempio
>>> class A: foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[5]
>>> class A:
... def __init__(self): self.foo = []
>>> a, b = A(), A()
>>> a.foo.append(5)
>>> b.foo
[]
Altri suggerimenti
La differenza è che l'attributo sulla classe è condiviso da tutte le istanze. L'attributo su un'istanza è univoco per quell'istanza.
Se provengono da C ++, gli attributi della classe sono più simili alle variabili statiche dei membri.
Ecco un ottimo post e riassumilo come qui di seguito.
class Bar(object):
## No need for dot syntax
class_var = 1
def __init__(self, i_var):
self.i_var = i_var
## Need dot syntax as we've left scope of class namespace
Bar.class_var
## 1
foo = MyClass(2)
## Finds i_var in foo's instance namespace
foo.i_var
## 2
## Doesn't find class_var in instance namespace…
## So look's in class namespace (Bar.__dict__)
foo.class_var
## 1
E in forma visiva
Assegnazione degli attributi di classe
-
Se un attributo di classe viene impostato accedendo alla classe, sovrascriverà il valore per tutte le istanze
foo = Bar(2) foo.class_var ## 1 Bar.class_var = 2 foo.class_var ## 2
-
Se una variabile di classe viene impostata accedendo a un'istanza, sovrascriverà il valore solo per quell'istanza . Questo essenzialmente sostituisce la variabile di classe e la trasforma in una variabile di istanza disponibile, intuitivamente, solo per quell'istanza .
foo = Bar(2) foo.class_var ## 1 foo.class_var = 2 foo.class_var ## 2 Bar.class_var ## 1
Quando useresti l'attributo class?
Memorizzazione di costanti . Poiché è possibile accedere agli attributi di classe come attributi della classe stessa, è spesso piacevole usarli per archiviare costanti specifiche della classe a livello di classe
class Circle(object): pi = 3.14159 def __init__(self, radius): self.radius = radius def area(self): return Circle.pi * self.radius * self.radius Circle.pi ## 3.14159 c = Circle(10) c.pi ## 3.14159 c.area() ## 314.159
Definizione dei valori predefiniti . Come esempio banale, potremmo creare un elenco limitato (ovvero un elenco che può contenere solo un determinato numero di elementi o meno) e scegliere di avere un limite predefinito di 10 elementi
class MyClass(object): limit = 10 def __init__(self): self.data = [] def item(self, i): return self.data[i] def add(self, e): if len(self.data) >= self.limit: raise Exception("Too many elements") self.data.append(e) MyClass.limit ## 10
Dato che le persone nei commenti qui e in altre due domande contrassegnate come duplicate sembrano tutte confuse allo stesso modo allo stesso modo, penso che valga la pena aggiungere una risposta aggiuntiva in cima a Alex Coventry's .
Il fatto che Alex stia assegnando un valore di tipo mutabile, come un elenco, non ha nulla a che fare con il fatto che le cose siano condivise o meno. Possiamo vederlo con la funzione id
o l'operatore is
:
>>> class A: foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
True
>>> class A:
... def __init__(self): self.foo = object()
>>> a, b = A(), A()
>>> a.foo is b.foo
False
(Se ti stai chiedendo perché ho usato object()
invece di, diciamo, 5
, è per evitare di imbattermi in altri due problemi che non voglio entrare qui; per due diversi motivi, i a.foo.append(5)
creati interamente separatamente possono finire per essere la stessa istanza del numero b.foo
. Ma i a.foo = 5
creati completamente separatamente non possono.)
Quindi, perché a.foo
nell'esempio di Alex influenza shared_ptr<T>
, ma T
nel mio esempio no? Bene, prova <=> nell'esempio di Alex e nota che non influisce <=> lì o .
<=> sta solo trasformando <=> in un nome per <=>. Ciò non influisce su <=> o su qualsiasi altro nome per il vecchio valore a cui <=> faceva riferimento. * È un po 'complicato creare un attributo di istanza che nasconda un attributo di classe, ** ma una volta che capito, non sta succedendo nulla di complicato qui.
Speriamo che ora sia ovvio il motivo per cui Alex ha usato un elenco: il fatto che puoi mutare un elenco significa che è più facile mostrare che due variabili nominano lo stesso elenco, e anche che è più importante nel codice della vita reale sapere se hai due elenchi o due nomi per lo stesso elenco.
* La confusione per le persone che provengono da un linguaggio come C ++ è che in Python i valori non sono memorizzati in variabili. I valori vivono nella terra dei valori, da soli, le variabili sono solo nomi per valori e l'assegnazione crea semplicemente un nuovo nome per un valore. Se aiuta, pensa a ciascuna variabile Python come <=> anziché <=>.
** Alcune persone ne approfittano usando un attributo class come " valore predefinito " per un attributo di istanza che le istanze possono o meno impostare. Questo può essere utile in alcuni casi, ma può anche essere fonte di confusione, quindi fai attenzione.
C'è ancora una situazione.
Gli attributi di classe e istanza sono Descrittore .
# -*- encoding: utf-8 -*-
class RevealAccess(object):
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
return self.val
class Base(object):
attr_1 = RevealAccess(10, 'var "x"')
def __init__(self):
self.attr_2 = RevealAccess(10, 'var "x"')
def main():
b = Base()
print("Access to class attribute, return: ", Base.attr_1)
print("Access to instance attribute, return: ", b.attr_2)
if __name__ == '__main__':
main()
In alto verrà visualizzato:
('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)
Lo stesso tipo di accesso all'istanza tramite classe o istanza restituisce risultati diversi!
E ho trovato nella c.PyObject_GenericGetAttr definition & # 65292; e un grande posta .
Spiegare
Se l'attributo si trova nel dizionario delle classi che compongono. gli oggetti MRO, quindi controlla se l'attributo cercato punta a un descrittore di dati (che non è altro che una classe che implementa i metodi
__get__
e__set__
). In tal caso, risolvi la ricerca dell'attributo chiamando il metodo <=> del descrittore di dati (righe 28 & # 8211; 33).