it-swarm.com.de

Was ist der Unterschied zwischen Klassen- und Instanzattributen?

Gibt es einen sinnvollen Unterschied zwischen:

class A(object):
    foo = 5   # some default value

vs.

class B(object):
    def __init__(self, foo=5):
        self.foo = foo

Wenn Sie viele Instanzen erstellen, unterscheiden sich die beiden Stile hinsichtlich Leistung und Speicherplatz? Halten Sie beim Lesen des Codes die Bedeutung der beiden Stile für wesentlich anders?

125
Dan Homerick

Neben den Überlegungen zur Leistung gibt es einen signifikanten semantischen Unterschied. Im Fall der Klassenattribute wird nur ein Objekt angegeben. In der Instanzattribut-Set-at-Instantiation können mehrere Objekte angegeben werden. Zum Beispiel

>>> 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    
[]
136
Alex Coventry

Der Unterschied besteht darin, dass das Attribut der Klasse von allen Instanzen gemeinsam genutzt wird. Das Attribut einer Instanz ist für diese Instanz eindeutig.

Wenn Sie aus C++ stammen, sind Attribute in der Klasse eher statische Elementvariablen.

35
Peter Shinners

Hier ist ein sehr guter Beitrag und eine Zusammenfassung wie unten.

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

Und in visueller Form

 enter image description here

Klassenattributzuweisung

  • Wenn ein Klassenattribut durch Zugriff auf die Klasse festgelegt wird, überschreibt es den Wert für alle Instanzen

    foo = Bar(2)
    foo.class_var
    ## 1
    Bar.class_var = 2
    foo.class_var
    ## 2
    
  • Wenn eine Klassenvariable durch Zugriff auf eine Instanz festgelegt wird, überschreibt sie den Wert nur für diese Instanz. Dadurch wird die Klassenvariable im Wesentlichen überschrieben und in eine Instanzvariable umgewandelt, die intuitiv nur für diese Instanz verfügbar ist.

    foo = Bar(2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    Bar.class_var
    ## 1
    

Wann würden Sie Klassenattribute verwenden?

  • Konstanten speichern. Da auf Klassenattribute als Attribute der Klasse selbst zugegriffen werden kann, ist es oft Nizza, sie zum Speichern von klassenweiten klassenspezifischen Konstanten zu verwenden

    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
    
  • Standardwerte definieren. Als triviales Beispiel können wir eine begrenzte Liste erstellen (d. H. Eine Liste, die nur eine bestimmte Anzahl von Elementen oder weniger enthalten kann) und eine Standardobergrenze von 10 Elementen wählen

    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
    
25
zangw

Da die Leute in den Kommentaren hier und in zwei anderen Fragen, die als dups gekennzeichnet sind, alle auf dieselbe Weise verwirrt zu sein scheinen, ist es meiner Meinung nach wert, zusätzlich zu Alex Coventry's eine zusätzliche Antwort hinzuzufügen.

Die Tatsache, dass Alex einen Wert eines veränderlichen Typs wie eine Liste zuweist, hat nichts damit zu tun, ob Dinge gemeinsam genutzt werden oder nicht. Wir können dies mit der Funktion id oder dem Operator is sehen:

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

(Wenn Sie sich fragen, warum ich object() anstelle von beispielsweise 5 verwendet habe, sollten Sie vermeiden, auf zwei andere Probleme zu stoßen, auf die ich hier nicht eingehen möchte: Aus zwei verschiedenen Gründen können 5s aus zwei verschiedenen Gründen enden dasselbe Exemplar der Nummer 5 ist. Aber ganz separat erstellte object()s können nicht.)


Warum betrifft a.foo.append(5) in Alex 'Beispiel b.foo, a.foo = 5 in meinem Beispiel jedoch nicht? Nun, versuchen Sie a.foo = 5 in Alex 'Beispiel, und beachten Sie, dass b.foo dort nicht beides betrifft. 

a.foo = 5 macht a.foo nur zu einem Namen für 5. Dies wirkt sich nicht auf b.foo oder irgendeinen anderen Namen für den alten Wert aus, auf den a.foo verwiesen hat. * Es ist ein bisschen schwierig, dass wir ein Instanzattribut erstellen, das ein Klassenattribut verbirgt, ** aber sobald Sie das bekommen, nichts kompliziert geschieht hier.


Hoffentlich ist es jetzt offensichtlich, warum Alex eine Liste verwendet hat: Die Tatsache, dass Sie eine Liste mutieren können, bedeutet, dass es einfacher ist, zu zeigen, dass zwei Variablen dieselbe Liste benennen, und dass es im realen Code wichtiger ist, zu wissen, ob Sie zwei Listen haben oder zwei Namen für dieselbe Liste.


* Die Verwirrung für Leute, die aus einer Sprache wie C++ kommen, ist, dass in Python Werte nicht in Variablen gespeichert werden. Werte leben allein in value-land, Variablen sind nur Namen für Werte, und die Zuweisung erstellt nur einen neuen Namen für einen Wert. Wenn es hilfreich ist, stellen Sie sich jede Python-Variable als shared_ptr<T> und nicht als T vor.

** Einige Benutzer nutzen dies, indem sie ein Klassenattribut als "Standardwert" für ein Instanzattribut verwenden, das von Instanzen festgelegt wird oder nicht. Dies kann in einigen Fällen nützlich sein, kann aber auch verwirrend sein, seien Sie also vorsichtig.

19
abarnert

Nur eine Ausarbeitung zu dem, was Alex Coventry gesagt hat, hat ein anderer Alex (Martelli) eine ähnliche Frage in der comp.lang.python-Newsgroup vor Jahren beantwortet. Er untersucht den semantischen Unterschied zwischen dem, was eine Person beabsichtigte, und dem, was sie bekam (unter Verwendung von Instanzvariablen).

http://groups.google.com/group/comp.lang.python/msg/5914d297aff35fae?hl=de

2
torial

Es gibt noch eine weitere Situation. 

Klassen- und Instanzattribute sind Descriptor.

# -*- 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 __== '__main__':
    main()

Oben wird ausgegeben:

('Access to class attribute, return: ', 10)
('Access to instance attribute, return: ', <__main__.RevealAccess object at 0x10184eb50>)

Die gleiche Art von Instanzzugriff über Klassen oder Instanzen liefert ein anderes Ergebnis!

Und ich fand in c.PyObject_GenericGetAttr Definition , und ein tolles post .

Erklären

Wenn das Attribut im Wörterbuch der Klassen gefunden wird, aus denen .. .. besteht. Überprüfen Sie anschließend, ob das nachgeschlagene Attribut auf einen Datendeskriptor verweist (was nichts anderes ist als eine Klasse, die sowohl die Methoden __get__ als auch die Methoden __set__ implementiert) ..__ Wenn dies der Fall ist, lösen Sie die Attributsuche auf, indem Sie die __get__-Methode des Data Descriptor aufrufen (Zeilen 28–33).

0
SimonShyu