Impossibile impostare gli attributi sull'istanza della classe "oggetto"


88

Quindi, stavo giocando con Python mentre rispondevo a questa domanda , e ho scoperto che questo non è valido:

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

a causa di un AttributeError: 'object' object has no attribute 'attr'. Tuttavia, con qualsiasi classe ereditata da object, è valida:

class Sub(object):
    pass

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

La stampa s.attrvisualizza "ciao" come previsto. Perché è così? Cosa specifica nella specifica del linguaggio Python che non è possibile assegnare attributi agli oggetti vanilla?


Pura supposizione: il objecttipo è immutabile e non è possibile aggiungere nuovi attributi? Sembra che abbia più senso.
Chris Lutz

2
@ S.Lott: vedi la prima riga di questa domanda. Pura curiosità.
Smashery

3
Il tuo titolo è fuorviante, stai cercando di impostare attributi su objectistanze di classe, non su objectclasse.
Discesa

Risposte:


130

Per supportare l'assegnazione di attributi arbitrari, un oggetto necessita di __dict__: un dict associato all'oggetto, in cui è possibile memorizzare attributi arbitrari. Altrimenti, non c'è nessun posto dove inserire nuovi attributi.

Un esempio di objectnon non portare in giro un __dict__- se così fosse, prima che il problema della dipendenza circolare orribile (poiché dict, come la maggior parte tutto il resto, eredita da object;-), ciò sella ogni oggetto in Python con un dizionario, il che significherebbe un overhead di molti byte per oggetto che attualmente non ha o necessita di un dict (essenzialmente, tutti gli oggetti che non hanno attributi assegnabili arbitrariamente non hanno o necessitano di un dict).

Ad esempio, utilizzando l'ottimo pymplerprogetto (puoi ottenerlo tramite svn da qui ), possiamo fare alcune misurazioni ...:

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

Non vorresti che ognuno intoccupasse 144 byte invece di solo 16, giusto? -)

Ora, quando crei una classe (ereditando da qualunque cosa), le cose cambiano ...:

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

... ora __dict__ è stato aggiunto (in più, un po 'più di overhead) - quindi dintun'istanza può avere attributi arbitrari, ma per questa flessibilità paghi un costo abbastanza elevato.

E se volessi messaggi intcon un solo attributo in più foobar...? È una necessità rara, ma Python offre un meccanismo speciale per lo scopo ...

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

... non è abbastanza piccola come una int, ti dispiacerebbe! (o anche i due int, uno il selfe uno il self.foobar- il secondo può essere riassegnato), ma sicuramente molto meglio di undint .

Quando la classe ha l' __slots__attributo speciale (una sequenza di stringhe), l' classistruzione (più precisamente, la metaclasse predefinita type) non fornisce a ogni istanza di quella classe un __dict__(e quindi la capacità di avere attributi arbitrari), solo un finito , insieme rigido di "slot" (fondamentalmente luoghi che possono contenere ciascuno un riferimento a un oggetto) con i nomi dati.

In cambio della flessibilità perduta, guadagni molti byte per istanza (probabilmente significativo solo se hai milioni di istanze in giro, ma ci sono casi d'uso per quello).


5
Questo spiega come il meccanismo è implementato ma non spiega perché è implementato in questo modo. Posso pensare ad almeno due o tre modi per implementare l'aggiunta di dict al volo che non avrà il rovescio della medaglia ma aggiungerà un po 'di semplicità.
Георги Кременлиев

Si noti che non vuoto __slots__non funzionano con i tipi a lunghezza variabile come str, tuplee in Python 3 anche int.
arekolek


È un'ottima spiegazione, ma ancora non risponde perché (o come) Subl' __dict__attributo e l'oggetto no, essendo che Suberedita da object, come viene __module__aggiunto quell'attributo (e altri simili ) nell'eredità? Potrebbe essere questa potrebbe essere una nuova domanda
Rodrigo E. Principe,

3
Un oggetto __dict__viene creato solo la prima volta che è necessario, quindi la situazione del costo della memoria non è così semplice come l' asizeofoutput fa sembrare. ( asizeofnon sa come evitare la __dict__materializzazione.) In questo esempio puoi vedere il dict che non si materializza fino a quando non è necessario , e puoi vedere uno dei percorsi del codice responsabili della __dict__materializzazione qui .
user2357112 supporta Monica

17

Come hanno detto altri rispondenti, an objectnon ha un file __dict__. objectè la classe base di tutti i tipi, incluso into str. Quindi tutto ciò che viene fornito da objectsarà un peso anche per loro. Anche qualcosa di semplice come un optional __dict__ avrebbe bisogno di un puntatore extra per ogni valore; questo sprecherebbe 4-8 byte aggiuntivi di memoria per ogni oggetto nel sistema, per un'utilità molto limitata.


Invece di fare un'istanza di una classe fittizia, in Python 3.3+, puoi (e dovresti) usarla types.SimpleNamespaceper questo.


4

È semplicemente dovuto all'ottimizzazione.

I dict sono relativamente grandi.

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

La maggior parte (forse tutte) le classi definite in C non hanno un dettato per l'ottimizzazione.

Se guardi il codice sorgente vedrai che ci sono molti controlli per vedere se l'oggetto ha un dict o meno.


1

Quindi, indagando sulla mia stessa domanda, ho scoperto questo sul linguaggio Python: puoi ereditare da cose come int e vedi lo stesso comportamento:

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

Presumo che l'errore alla fine sia dovuto al fatto che la funzione add restituisce un int, quindi dovrei sovrascrivere funzioni simili __add__e simili per mantenere i miei attributi personalizzati. Ma tutto questo ora ha senso per me (credo), quando penso a "oggetto" come "int".


0

È perché l'oggetto è un "tipo", non una classe. In generale, tutte le classi definite nelle estensioni C (come tutti i tipi di dati incorporati e cose come gli array numpy) non consentono l'aggiunta di attributi arbitrari.


Ma object () è un oggetto, proprio come Sub () è un oggetto. La mia comprensione è che sia se o che sono oggetti. Allora qual è la differenza fondamentale tra s e o? È che uno è un tipo istanziato e l'altro è una classe istanziata?
Smashery

Bingo. Questo è esattamente il problema.
Ryan

1
In Python 3, la differenza tra tipi e classi non esiste realmente. Quindi "tipo" e "classe" sono ora abbastanza sinonimi. Ma non puoi ancora aggiungere attributi a quelle classi che non hanno un __dict__, per le ragioni fornite da Alex Martelli.
PM 2 Ring


-2

Questo è (IMO) uno dei limiti fondamentali con Python: non puoi riaprire le classi. Credo che il vero problema, tuttavia, sia causato dal fatto che le classi implementate in C non possono essere modificate in fase di esecuzione ... le sottoclassi possono, ma non le classi base.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.