Impossible de définir des attributs sur l'instance de la classe «objet»

87

Donc, je jouais avec Python en répondant à cette question , et j'ai découvert que ce n'est pas valide:

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

en raison d'un AttributeError: 'object' object has no attribute 'attr'. Cependant, avec n'importe quelle classe héritée de object, elle est valide:

class Sub(object):
    pass

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

L'impression s.attraffiche «bonjour» comme prévu. pourquoi est-ce le cas? Qu'est-ce qui dans la spécification du langage Python spécifie que vous ne pouvez pas attribuer d'attributs aux objets vanilla?

Smashery
la source
Pure conjecture: le objecttype est immuable et de nouveaux attributs ne peuvent pas être ajoutés? Cela semble être le plus logique.
Chris Lutz
2
@ S.Lott: Voir la toute première ligne de cette question. Purement curiosité.
Smashery
3
Votre titre est trompeur, vous essayez de définir des attributs sur objectdes instances de classe, pas sur une objectclasse.
dhill

Réponses:

129

Pour prendre en charge l'attribution arbitraire d'attributs, un objet a besoin d'un __dict__: un dict associé à l'objet, où des attributs arbitraires peuvent être stockés. Sinon, il n'y a nulle part où mettre de nouveaux attributs.

Une instance de objectne transporte pas un __dict__- si c'était le cas, avant l'horrible problème de dépendance circulaire (puisque dict, comme presque tout le reste, hérite de object;-), cela mettrait en selle chaque objet en Python avec un dict, ce qui signifierait une surcharge de nombreux octets par objet qui n'a actuellement pas ou n'a pas besoin d'un dict (essentiellement, tous les objets qui n'ont pas d'attributs assignables arbitrairement n'ont pas ou n'ont pas besoin d'un dict).

Par exemple, en utilisant l'excellent pymplerprojet (vous pouvez l'obtenir via svn à partir d' ici ), nous pouvons faire quelques mesures ...:

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

Vous ne voudriez pas que chacun intprenne 144 octets au lieu de seulement 16, non? -)

Maintenant, quand vous créez une classe (héritant de quoi que ce soit), les choses changent ...:

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

... le __dict__ est maintenant ajouté (en plus, un peu plus de frais généraux) - une dintinstance peut donc avoir des attributs arbitraires, mais vous payez un coût d'espace assez élevé pour cette flexibilité.

Et si vous vouliez des ints avec un seul attribut supplémentaire foobar...? C'est un besoin rare, mais Python offre un mécanisme spécial à cet effet ...

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

...ne pas tout à fait aussi petit comme intvous l' esprit! (ou même les deux ints, un le selfet un le self.foobar- le second peut être réaffecté), mais sûrement beaucoup mieux qu'un dint.

Lorsque la classe a le __slots__ attribut spécial (une séquence de chaînes), alors l' classinstruction (plus précisément, la métaclasse par défaut, type) n'équipe pas chaque instance de cette classe avec un __dict__(et donc la possibilité d'avoir des attributs arbitraires), juste un fini , ensemble rigide de "slots" (essentiellement des endroits qui peuvent chacun contenir une référence à un objet) avec les noms donnés.

En échange de la flexibilité perdue, vous gagnez beaucoup d'octets par instance (probablement significatif uniquement si vous avez des millions d'instances qui circulent, mais il existe des cas d'utilisation pour cela).

Alex Martelli
la source
5
Ceci explique comment le mécanisme est mis en œuvre mais n'explique pas pourquoi il est mis en œuvre de cette manière. Je peux penser à au moins deux ou trois façons d'implémenter l'ajout de dict à la volée, ce qui n'aura pas d'inconvénient mais ajoutera de la simplicité.
Георги Кременлиев
Notez que non vide __slots__ne fonctionnent pas avec les types de longueur variable tels que str, tupleet en Python 3 aussi int.
arekolek
C'est une excellente explication, mais ne répond toujours pas pourquoi (ou comment) Subl' __dict__attribut et l'objet ne le font pas, étant donné qu'ils Subhéritent object, comment cet attribut (et d'autres comme __module__) est-il ajouté à l'héritage? Peut-être que cela pourrait être une nouvelle question
Rodrigo E. Principe
2
Un objet __dict__n'est créé que la première fois qu'il est nécessaire, donc la situation de coût de la mémoire n'est pas aussi simple que la asizeofsortie le laisse paraître. ( asizeofne sait pas comment éviter la __dict__matérialisation.) Vous pouvez voir que le dict ne se matérialise pas tant qu'il n'est pas nécessaire dans cet exemple , et vous pouvez voir l'un des chemins de code responsables de la __dict__matérialisation ici .
user2357112 prend en charge Monica le
17

Comme l'ont dit d'autres répondants, an objectn'a pas de __dict__. objectest la classe de base de tous les types, y compris intou str. Ainsi, tout ce qui leur est fourni objectsera également un fardeau pour eux. Même quelque chose d'aussi simple qu'un optionnel __dict__ aurait besoin d'un pointeur supplémentaire pour chaque valeur; cela gaspillerait 4 à 8 octets supplémentaires de mémoire pour chaque objet du système, pour une utilité très limitée.


Au lieu de faire une instance d'une classe factice, en Python 3.3+, vous pouvez (et devriez) utiliser types.SimpleNamespacepour cela.

Antti Haapala
la source
4

C'est simplement dû à l'optimisation.

Les dictionnaires sont relativement volumineux.

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

La plupart (peut-être toutes) les classes définies en C n'ont pas de dict pour l'optimisation.

Si vous regardez le code source, vous verrez qu'il existe de nombreuses vérifications pour voir si l'objet a un dict ou non.

Inconnue
la source
1

Alors, en étudiant ma propre question, j'ai découvert ceci à propos du langage Python: vous pouvez hériter de choses comme int, et vous voyez le même comportement:

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

Je suppose que l'erreur à la fin est due au fait que la fonction add renvoie un int, donc je devrais remplacer des fonctions comme __add__et telles afin de conserver mes attributs personnalisés. Mais tout cela a maintenant un sens pour moi (je pense), quand je pense à "objet" comme "int".

Smashery
la source
0

C'est parce que l'objet est un "type", pas une classe. En général, toutes les classes définies dans les extensions C (comme tous les types de données intégrés et des trucs comme les tableaux numpy) ne permettent pas l'ajout d'attributs arbitraires.

Ryan
la source
Mais object () est un objet, tout comme Sub () est un objet. Je crois comprendre que s et o sont des objets. Alors, quelle est la différence fondamentale entre s et o? Est-ce que l'un est un type instancié et l'autre est une classe instanciée?
Smashery
Bingo. C'est exactement le problème.
Ryan
1
Dans Python 3, la différence entre les types et les classes n'existe pas vraiment. Donc, "type" et "classe" sont maintenant assez synonymes. Mais vous ne pouvez toujours pas ajouter d'attributs à ces classes qui n'ont pas de __dict__, pour les raisons données par Alex Martelli.
PM 2Ring
-2

C'est (IMO) l'une des limitations fondamentales de Python - vous ne pouvez pas rouvrir les classes. Je pense que le problème réel, cependant, est causé par le fait que les classes implémentées en C ne peuvent pas être modifiées au moment de l'exécution ... les sous-classes le peuvent, mais pas les classes de base.

Peter
la source