vererbung - Python: Unterschied zwischen Klassen- und Instanzattributen




python vererbung private attribute (4)

Abgesehen von Leistungsüberlegungen gibt es einen signifikanten semantischen Unterschied. Im Fall des Klassenattributs gibt es nur ein Objekt, auf das verwiesen wird. In der Instanzattributmenge-bei-Instanziierung kann auf mehrere Objekte Bezug genommen 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    
[]

Gibt es eine sinnvolle Unterscheidung zwischen:

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

gegen

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

Wenn Sie viele Instanzen erstellen, gibt es Unterschiede in den Leistungs- oder Platzanforderungen für die beiden Stile? Wenn Sie den Code lesen, betrachten Sie die Bedeutung der beiden Stile als signifikant unterschiedlich?


Da die Leute in den Kommentaren hier und in zwei anderen Fragen, die als Duplikate gekennzeichnet sind, alle auf dieselbe Weise verwirrt sind, denke ich, dass es sich lohnt, eine zusätzliche Antwort auf Alex Coventrys 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 id Funktion oder dem Operator 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

(Wenn Sie sich fragen, warum ich object() anstelle von, sagen wir, 5 , vermeiden Sie zwei weitere Probleme, auf die ich hier nicht näher eingehen möchte, und zwar aus zwei verschiedenen Gründen, die ganz separat erstellt wurden kann am Ende die gleiche Instanz der Nummer 5 aber völlig separat erstellt object() s nicht.)

Warum beeinflusst a.foo.append(5) in Alex 'Beispiel b.foo , aber a.foo = 5 in meinem Beispiel nicht? Nun, versuchen Sie a.foo = 5 in Alex 'Beispiel, und beachten Sie, dass es b.foo dort auch nicht beeinflusst.

a.foo = 5 macht gerade a.foo zu einem Namen für 5 . Das hat keinen Einfluss auf b.foo oder irgendeinen anderen Namen für den alten Wert, auf den sich a.foo bezieht. * Es ist ein wenig schwierig, dass wir ein a.foo erstellen, das ein Klassenattribut verbirgt, ** aber erst einmal versteh das, hier passiert nichts kompliziertes.

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 die gleiche Liste benennen, und dass es im echten Code wichtiger ist zu wissen, ob Sie zwei Listen haben zwei Namen für die gleiche 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 erzeugt nur einen neuen Namen für einen Wert. Wenn es hilft, denke an jede Python-Variable als shared_ptr<T> anstelle von T

** Einige Benutzer nutzen dies, indem sie ein Klassenattribut als "Standardwert" für ein Instanzattribut verwenden, das Instanzen möglicherweise festlegen. Dies kann in einigen Fällen nützlich sein, aber es kann auch verwirrend sein, also sei vorsichtig damit.


Es gibt noch eine weitere 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>)

Der gleiche Typ von Instanzzugriff über Klasse oder Instanz gibt ein anderes Ergebnis zurück!

Und ich fand in c.PyObject_GenericGetAttr Definition und einen tollen post .

Erklären

Wenn das Attribut im Wörterbuch der Klassen gefunden wird, aus denen es besteht. die Objekte MRO, dann prüfen, ob das nachgeschlagene Attribut auf einen Datenbeschreiber zeigt (was nichts anderes ist als eine Klasse, die sowohl die __get__ als auch die __set__ Methoden __set__ ). Wenn dies der Fall ist, lösen Sie die Attributsuche auf, indem Sie die Methode __get__ des Datenbeschreibers (Zeilen 28-33) aufrufen.


Hier ist ein sehr guter post , und zusammenfassend wie folgt.

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

Klassenattribut-Zuweisung

  • 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 gesetzt wird, überschreibt sie den Wert nur für diese Instanz . Dies überschreibt im Wesentlichen die Klassenvariable und wandelt sie in eine Instanzvariable um, 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 klassenweiter, klassenspezifischer 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önnten wir eine beschränkte 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
    






attributes