Attribute für Instanz der Objektklasse können nicht festgelegt werden

86

Also habe ich bei der Beantwortung dieser Frage mit Python herumgespielt und festgestellt, dass dies nicht gültig ist:

o = object()
o.attr = 'hello'

aufgrund eines AttributeError: 'object' object has no attribute 'attr'. Bei jeder vom Objekt geerbten Klasse gilt jedoch Folgendes:

class Sub(object):
    pass

s = Sub()
s.attr = 'hello'

Beim Drucken s.attrwird wie erwartet "Hallo" angezeigt. Warum ist das so? Was in der Python-Sprachspezifikation gibt an, dass Sie Vanilleobjekten keine Attribute zuweisen können?

Smashery
quelle
Rätselraten: Der objectTyp ist unveränderlich und neue Attribute können nicht hinzugefügt werden? Dies scheint am sinnvollsten zu sein.
Chris Lutz
2
@ S.Lott: Siehe die allererste Zeile dieser Frage. Rein neugierig.
Smashery
3
Ihr Titel ist irreführend. Sie versuchen, Attribute für objectKlasseninstanzen und nicht für objectKlassen festzulegen.
Dhill

Antworten:

128

Um die Zuweisung beliebiger Attribute zu unterstützen, benötigt ein Objekt Folgendes __dict__: ein dem Objekt zugeordnetes Diktat, in dem beliebige Attribute gespeichert werden können. Ansonsten gibt es nirgendwo zu setzen neue Attribute.

Eine Instanz von objectträgt nicht ein __dict__- wenn ja, vor dem schrecklichen Problem der zirkulären Abhängigkeit (da dict, wie fast alles andere, von object;-) erbt , würde dies jedes Objekt in Python mit einem Diktat satteln , was einen Overhead bedeuten würde von vielen Bytes pro Objekt, die derzeit kein Diktat haben oder benötigen (im Wesentlichen haben oder benötigen alle Objekte, die keine willkürlich zuweisbaren Attribute haben, kein Diktat).

Mit dem hervorragenden pymplerProjekt (das Sie hier über svn erhalten können ) können wir beispielsweise einige Messungen durchführen ...:

>>> from pympler import asizeof
>>> asizeof.asizeof({})
144
>>> asizeof.asizeof(23)
16

Sie möchten nicht, dass jeder int144 Bytes statt nur 16 Bytes belegt, oder? -)

Wenn Sie nun eine Klasse erstellen (von was auch immer erben), ändern sich die Dinge ...:

>>> class dint(int): pass
... 
>>> asizeof.asizeof(dint(23))
184

... das __dict__ wird jetzt hinzugefügt (plus etwas mehr Overhead) - so kann eine dintInstanz beliebige Attribute haben, aber Sie zahlen ziemlich viel Platz für diese Flexibilität.

Was wäre, wenn Sie ints mit nur einem zusätzlichen Attribut wollten foobar...? Es ist eine seltene Notwendigkeit, aber Python bietet einen speziellen Mechanismus für diesen Zweck ...

>>> class fint(int):
...   __slots__ = 'foobar',
...   def __init__(self, x): self.foobar=x+100
... 
>>> asizeof.asizeof(fint(23))
80

... nicht ganz so klein wie ein int, wohlgemerkt! (oder sogar die zwei ints, eins das selfund eins das self.foobar- das zweite kann neu zugewiesen werden), aber sicherlich viel besser als a dint.

Wenn die Klasse das __slots__spezielle Attribut (eine Folge von Zeichenfolgen) hat, rüstet die classAnweisung (genauer gesagt die Standard-Metaklasse type) nicht jede Instanz dieser Klasse mit einem __dict__(und daher der Fähigkeit, beliebige Attribute zu haben) aus, sondern nur mit einem Endlichen , starrer Satz von "Schlitzen" (im Grunde Orte, die jeweils einen Verweis auf ein Objekt enthalten können) mit den angegebenen Namen.

Als Gegenleistung für die verlorene Flexibilität erhalten Sie eine Menge Bytes pro Instanz (wahrscheinlich nur dann sinnvoll, wenn Sie zig Instanzen haben, die herum galoppieren, aber es gibt Anwendungsfälle dafür).

Alex Martelli
quelle
5
Dies erklärt, wie der Mechanismus implementiert wird, erklärt jedoch nicht, warum er auf diese Weise implementiert wird. Ich kann mir mindestens zwei oder drei Möglichkeiten vorstellen , um das Hinzufügen von Diktaten im laufenden Betrieb zu implementieren , die nicht den Overhead-Nachteil haben, aber eine gewisse Einfachheit hinzufügen.
3еорги Кременлиев
Beachten Sie, dass nicht leer ist __slots__nicht Arbeit mit variabler Länge Typen wie str, tupleund in Python 3 auch int.
Arekolek
Es ist eine großartige Erklärung, antwortet aber immer noch nicht, warum (oder wie) Subdas __dict__Attribut und das Objekt nicht haben, da es so Suberbt object, wie dieses Attribut (und andere wie __module__) in der Vererbung hinzugefügt werden? Vielleicht könnte dies eine neue Frage sein
Rodrigo E. Principe
2
Ein Objekt __dict__wird nur beim ersten Mal erstellt, wenn es benötigt wird. Daher ist die Speicherkostensituation nicht ganz so einfach, wie es die asizeofAusgabe aussehen lässt. ( asizeofweiß nicht, wie man __dict__Materialisierung vermeidet .) Sie können sehen, dass das Diktat erst materialisiert wird, wenn es in diesem Beispiel benötigt wird , und Sie können hier einen der Codepfade sehen, die für die __dict__Materialisierung verantwortlich sind .
user2357112 unterstützt Monica
17

Wie andere Antwortende gesagt haben, hat ein objectkein __dict__. objectist die Basisklasse aller Typen, einschließlich intoder str. Was auch immer von bereitgestellt wird, objectwird auch für sie eine Belastung sein. Selbst etwas so Einfaches wie ein Optionales __dict__ würde einen zusätzlichen Zeiger für jeden Wert benötigen. Dies würde zusätzliche 4-8 Bytes Speicher für jedes Objekt im System verschwenden, was für einen sehr begrenzten Nutzen von Bedeutung ist.


Anstatt eine Instanz einer Dummy-Klasse zu erstellen, können (und sollten) Sie in Python 3.3+ types.SimpleNamespacedies verwenden.

Antti Haapala
quelle
4

Es liegt einfach an der Optimierung.

Diktate sind relativ groß.

>>> import sys
>>> sys.getsizeof((lambda:1).__dict__)
140

Die meisten (möglicherweise alle) Klassen, die in C definiert sind, haben kein Optimierungsdiktat.

Wenn Sie sich den Quellcode ansehen, werden Sie feststellen, dass es viele Überprüfungen gibt, um festzustellen, ob das Objekt ein Diktat hat oder nicht.

Unbekannt
quelle
1

Als ich meine eigene Frage untersuchte, entdeckte ich Folgendes über die Python-Sprache: Sie können von Dingen wie int erben, und Sie sehen dasselbe Verhalten:

>>> class MyInt(int):
       pass

>>> x = MyInt()
>>> print x
0
>>> x.hello = 4
>>> print x.hello
4
>>> x = x + 1
>>> print x
1
>>> print x.hello
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
AttributeError: 'int' object has no attribute 'hello'

Ich gehe davon aus, dass der Fehler am Ende darin besteht, dass die Funktion add ein int zurückgibt. Daher müsste ich Funktionen wie __add__und so überschreiben , um meine benutzerdefinierten Attribute beizubehalten. Aber das alles macht jetzt Sinn für mich (denke ich), wenn ich an "Objekt" wie "int" denke.

Smashery
quelle
0

Das liegt daran, dass das Objekt ein "Typ" ist, keine Klasse. Im Allgemeinen erlauben alle Klassen, die in C-Erweiterungen definiert sind (wie alle integrierten Datentypen und solche wie Numpy-Arrays), das Hinzufügen beliebiger Attribute nicht.

Ryan
quelle
Aber object () ist ein Objekt, genau wie Sub () ein Objekt ist. Mein Verständnis ist, dass sowohl s als auch o Objekte sind. Was ist der grundlegende Unterschied zwischen s und o? Ist einer ein instanziierter Typ und der andere eine instanziierte Klasse?
Smashery
Bingo. Genau das ist das Problem.
Ryan
1
In Python 3 besteht der Unterschied zwischen Typen und Klassen nicht wirklich. "Typ" & "Klasse" sind also jetzt ziemlich synonym. __dict__Aus den von Alex Martelli angegebenen Gründen können Sie den Klassen, die keine haben , jedoch immer noch keine Attribute hinzufügen .
PM 2Ring
-2

Dies ist (IMO) eine der grundlegenden Einschränkungen bei Python - Sie können Klassen nicht erneut öffnen. Ich glaube jedoch, dass das eigentliche Problem durch die Tatsache verursacht wird, dass in C implementierte Klassen zur Laufzeit nicht geändert werden können ... Unterklassen können, aber nicht die Basisklassen.

Peter
quelle