2009-10-07 12 views
64

Also, ich wurde um mit Python zu spielen, während this question zu beantworten, und ich entdeckte, dass diese nicht gültig ist:Kann nicht Attribute von Objektklasse gesetzt

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

aufgrund eines AttributeError: 'object' object has no attribute 'attr'. Doch mit jeder Klasse von Objekt geerbt, es gilt:

class Sub(object): 
    pass 

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

Printing s.attr Displays 'Hallo', wie erwartet. Warum ist das der Fall? Was in der Python-Sprachspezifikation gibt an, dass Sie Vanilla-Objekten keine Attribute zuweisen können?

+0

Reine Vermutung: Der 'Objekt'-Typ ist unveränderlich und neue Attribute können nicht hinzugefügt werden? Das scheint so, als wäre es am sinnvollsten. –

+2

@ S.Lott: Siehe die erste Zeile dieser Frage. Nur Neugier. – Smashery

+2

Ihr Titel ist irreführend, Sie versuchen, Attribute für Klassenobjekte "object" und nicht für die Klasse "object" festzulegen. – dhill

Antwort

107

Um eine beliebige Attributzuweisung zu unterstützen, benötigt ein Objekt eine : ein dem Objekt zugeordnetes Diktat, in dem beliebige Attribute gespeichert werden können. Ansonsten gibt es keine neue Attribute.

Eine Instanz object tut nicht carry um einen __dict__ - wenn es so wäre, vor dem schrecklichen Kreis Abhängigkeit Problem (da dict, wie die meisten alles andere, erbt von object ;-), würde dies Sattel jeden Objekt in Python mit einem dict, was einen Overhead von viele Bytes pro Objekt bedeuten würde, die derzeit kein dict haben oder brauchen (im Wesentlichen haben alle Objekte, die keine willkürlich zuweisbaren Attribute haben, kein dict oder brauchen es)).

Zum Beispiel die hervorragende pympler Projekt mit (Sie können es über svn von here erhalten), können wir einige Messungen tun ...:

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

Sie würden nicht alle int wollen 144 aufzunehmen Bytes statt nur 16, rechts -)

wenn Sie nun eine Klasse machen (von was auch immer vererben), die Dinge ändern sich ...:

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

... die __dict__ist jetzt hinzugefügt (plus, ein wenig mehr Overhead) - so eine dint Instanz kann beliebige Attribute haben, aber Sie bezahlen ziemlich viel Platz für diese Flexibilität.So

was, wenn man wollte int s mit nur einem zusätzlichem Attribute foobar ...? Es ist eine seltene Notwendigkeit, aber Python hat zum Zweck einen speziellen Mechanismus bietet ...

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

... nicht ganz so winzig wie ein int wohlgemerkt! (oder sogar die beiden int s, eine der self und eine der self.foobar - die zweite kann neu zugewiesen werden), aber sicherlich viel besser als ein dint.

Wenn die Klasse das __slots__ spezielle Attribut (eine Folge von Strings) hat, dann ist die class Aussage (genauer gesagt, die Standard-Metaklasse, type) tun nicht jede Instanz dieser Klasse mit einem __dict__ auszustatten (und damit die Fähigkeit, willkürliche Attribute zu haben), nur eine endliche, starre Menge von "Slots" (im Grunde Orte, die jeweils eine Referenz auf ein Objekt haben können) mit den gegebenen Namen.

Im Austausch für die verlorene Flexibilität, erhalten Sie eine Menge von Bytes pro Instanz (wahrscheinlich nur sinnvoll, wenn Sie zig Millionen von Instanzen gallivanting haben, aber sind Anwendungsfälle dafür).

+5

Dies erklärt, wie der Mechanismus implementiert wird, erklärt aber nicht, warum er auf diese Weise implementiert wird.Ich kann mir mindestens zwei oder drei Möglichkeiten vorstellen, __dict__ on the fly zu implementieren, was nicht den Overhead-Nachteil mit sich bringt, aber eine gewisse Einfachheit bringt. –

+0

Beachten Sie, dass nicht leere '__slots__' nicht mit Typen variabler Länge wie' str', 'tuple' und in Python3 auch' int' funktionieren. – arekolek

+0

Schön und danke! [Wenn ein Objekt nicht "__dict__" hat, muss seine Klasse ein Attribut "__slot__" haben? (Https://stackoverflow.com/q/46575174/156458) – Tim

-1

Dies ist (IMO) eine der grundlegenden Einschränkungen mit Python - Sie können Klassen nicht wieder öffnen. Ich glaube, dass das tatsächliche 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.

1

Es ist, weil Objekt ein "Typ" ist, keine Klasse. Im Allgemeinen erlauben alle Klassen, die in C-Erweiterungen definiert sind (wie alle eingebauten Datentypen und solche wie numpy Arrays), keine zusätzlichen Attribute.

+0

Aber object() ist ein Objekt, genauso 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 der eine ein instanziierter Typ und der andere eine instanziierte Klasse? – Smashery

+0

Bingo. Das ist genau das Problem. – Ryan

1

Also, meine eigene Frage zu untersuchen, ich dies über die Sprache Python entdeckt: Sie Dinge wie int erben kann, und Sie sehen das gleiche 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 nehme den Fehler am Ende ist, weil Die add-Funktion gibt einen int zurück, also müsste ich Funktionen wie __add__ und dergleichen überschreiben, um meine benutzerdefinierten Attribute beizubehalten. Aber das alles macht jetzt für mich (denke ich) Sinn, wenn ich an "Objekt" wie "int" denke.

5

Es ist einfach wegen der Optimierung.

Dicts sind relativ groß.

Die meisten (möglicherweise alle) Klassen, die in C definiert sind, haben kein Diktat für die Optimierung.

Wenn Sie sich die source code ansehen, werden Sie sehen, dass es viele Überprüfungen gibt, um zu sehen, ob das Objekt ein Diktat hat oder nicht.

16

Wie andere Beantworter gesagt haben, hat eine object keine __dict__. object ist die Basisklasse von alle Typen, einschließlich int oder str. Also, was auch immer von object zur Verfügung gestellt wird, wird eine Belastung für sie 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 in dem System verschwenden, für einen sehr begrenzten Nutzen.


Anstatt eine Instanz einer Dummy-Klasse zu tun, in Python 3.3+, können Sie (und sollte) types.SimpleNamespace für diese.

Verwandte Themen