Was ist der Unterschied zwischen Klassen- und Instanzattributen?

132

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, gibt es Unterschiede in der Leistung oder im Platzbedarf für die beiden Stile? Betrachten Sie beim Lesen des Codes die Bedeutung der beiden Stile als erheblich unterschiedlich?

Dan Homerick
quelle
1
Ich habe gerade festgestellt, dass hier eine ähnliche Frage gestellt und beantwortet wurde: stackoverflow.com/questions/206734/… Soll ich diese Frage löschen?
Dan Homerick
2
Es ist Ihre Frage, zögern Sie nicht, sie zu löschen. Warum sollten Sie die Meinung eines anderen fragen, da es Ihnen gehört?
S.Lott

Antworten:

146

Über Leistungsüberlegungen hinaus gibt es einen signifikanten semantischen Unterschied. Im Fall eines Klassenattributs wird nur auf ein Objekt verwiesen. In der Instanzattributmenge, die bei der Instanziierung festgelegt wird, können mehrere Objekte referenziert 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    
[]
Alex Coventry
quelle
4
Nur die veränderlichen Typen werden gemeinsam genutzt. Wie für intund strsie hängen immer noch mit jeder Instanz und nicht mit der Klasse zusammen.
Babu
11
@Babu: Nein, intund strwerden auch genauso geteilt. Sie können dies einfach mit isoder überprüfen id. Oder schauen Sie einfach in die einzelnen Instanzen __dict__und Klassen __dict__. Es spielt normalerweise keine Rolle, ob unveränderliche Typen gemeinsam genutzt werden oder nicht.
Abarnert
17
Beachten Sie jedoch a.foo = 5, dass in beiden Fällen eine b.fooRückkehr angezeigt wird []. Dies liegt daran, dass Sie im ersten Fall das Klassenattribut a.foomit einem neuen Instanzattribut mit demselben Namen überschreiben .
Konstantin Schubert
39

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, ähneln die Attribute der Klasse eher statischen Elementvariablen.

Peter Shinners
quelle
1
Werden nicht nur veränderbare Typen gemeinsam genutzt? Die akzeptierte Antwort zeigt eine Liste, die funktioniert, aber wenn es sich um ein int handelt, scheint es dasselbe zu sein wie eine Instanz attr: >>> class A(object): foo = 5 >>> a, b = A(), A() >>> a.foo = 10 >>> b.foo 5
Rafe
6
@Rafe: Nein, alle Typen werden gemeinsam genutzt. Der Grund , warum Sie verwirrt sind , ist das , was a.foo.append(5)den Wert mutiert , die a.foosich bezieht, während a.foo = 5macht a.foofür den Wert in einen neuen Namen 5. Sie erhalten also ein Instanzattribut, das das Klassenattribut verbirgt. Versuchen Sie dasselbe a.foo = 5in Alex 'Version und Sie werden sehen, dass dies b.foounverändert ist.
Abarnert
30

Hier ist ein sehr guter Beitrag und fasse ihn wie folgt zusammen.

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

Geben Sie hier die Bildbeschreibung ein

Zuweisung von Klassenattributen

  • Wenn ein Klassenattribut durch Zugriff auf die Klasse festgelegt wird, wird der Wert für alle Instanzen überschrieben

    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, wird der Wert nur für diese Instanz überschrieben . Dies überschreibt im Wesentlichen die Klassenvariable und verwandelt sie in eine Instanzvariable, 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 das Klassenattribut verwenden?

  • Konstanten speichern . Da auf Klassenattribute als Attribute der Klasse selbst zugegriffen werden kann, ist es oft hilfreich, 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 (dh eine Liste, die nur eine bestimmte Anzahl von Elementen oder weniger enthalten kann) und eine Standardobergrenze von 10 Elementen festlegen

    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
zangw
quelle
19

Da die Leute in den Kommentaren hier und in zwei anderen Fragen, die als Dups markiert sind, alle auf die gleiche Weise verwirrt zu sein scheinen, denke ich, dass es sich lohnt, zusätzlich zu Alex Coventrys 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 geteilt werden oder nicht. Wir können dies mit der idFunktion oder dem isOperator 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 verwendet habe, 5um zu vermeiden, dass ich auf zwei ganz andere Probleme stoße, auf die ich hier nicht eingehen möchte. Aus zwei verschiedenen Gründen können vollständig separat erstellte 5s die sein gleiche Instanz der Nummer 5. Aber völlig separat erstellte object()s können nicht.)


Warum a.foo.append(5)wirkt sich das in Alex 'Beispiel aus b.foo, a.foo = 5in meinem Beispiel jedoch nicht? Nun, versuchen a.foo = 5in Alex Beispiel, und bemerkt , dass es nicht wirkt sich b.foodort entweder .

a.foo = 5macht nur a.fooeinen Namen für 5. Dies wirkt sich nicht auf b.fooeinen anderen Namen für den alten Wert aus, auf den a.foofrüher Bezug genommen wurde. * Es ist etwas schwierig, ein Instanzattribut zu erstellen, das ein Klassenattribut verbirgt. ** Sobald Sie das erhalten, ist nichts kompliziertes passiert hier.


Hoffentlich ist jetzt klar, 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 im Werteland von sich aus, Variablen sind nur Namen für Werte, und die Zuweisung erstellt nur einen neuen Namen für einen Wert. Wenn es hilft, stellen Sie sich jede Python-Variable als eine shared_ptr<T>anstelle von a vor T.

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

abarnert
quelle
0

Es gibt noch eine Situation.

Klassen- und Instanzattribute sind Deskriptor .

# -*- 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()

Oben wird ausgegeben:

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

Dieselbe Art von Instanzzugriff über Klasse oder Instanz liefert unterschiedliche Ergebnisse!

Und ich fand in c.PyObject_GenericGetAttr Definition, und einen tollen Beitrag .

Erklären

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

SimonShyu
quelle