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?

È stato utile?

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

invio descrizione dell'immagine qui

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).

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