Can't set attributes on instance of "object" class
Asked Answered
T

7

98

So, I was playing around with Python while answering this question, and I discovered that this is not valid:

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

due to an AttributeError: 'object' object has no attribute 'attr'. However, with any class inherited from object, it is valid:

class Sub(object):
    pass

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

Printing s.attr displays 'hello' as expected. Why is this the case? What in the Python language specification specifies that you can't assign attributes to vanilla objects?


For other workarounds, see How can I create an object and add attributes to it?.

Trapezius answered 7/10, 2009 at 1:7 Comment(3)
Pure guesswork: The object type is immutable and new attributes cannot be added? This seems like it would make the most sense.Dated
@ S.Lott: See the very first line of this question. Purely curiosity.Trapezius
Your title is misleading, you are trying to set attributes on object class instances, not on object class.Quadrate
R
144

To support arbitrary attribute assignment, an object needs a __dict__: a dict associated with the object, where arbitrary attributes can be stored. Otherwise, there's nowhere to put new attributes.

An instance of object does not carry around a __dict__ -- if it did, before the horrible circular dependence problem (since dict, like most everything else, inherits from object;-), this would saddle every object in Python with a dict, which would mean an overhead of many bytes per object that currently doesn't have or need a dict (essentially, all objects that don't have arbitrarily assignable attributes don't have or need a dict).

For example, using the excellent pympler project (you can get it via svn from here), we can do some measurements...:

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

You wouldn't want every int to take up 144 bytes instead of just 16, right?-)

Now, when you make a class (inheriting from whatever), things change...:

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

...the __dict__ is now added (plus, a little more overhead) -- so a dint instance can have arbitrary attributes, but you pay quite a space cost for that flexibility.

So what if you wanted ints with just one extra attribute foobar...? It's a rare need, but Python does offer a special mechanism for the purpose...

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

...not quite as tiny as an int, mind you! (or even the two ints, one the self and one the self.foobar -- the second one can be reassigned), but surely much better than a dint.

When the class has the __slots__ special attribute (a sequence of strings), then the class statement (more precisely, the default metaclass, type) does not equip every instance of that class with a __dict__ (and therefore the ability to have arbitrary attributes), just a finite, rigid set of "slots" (basically places which can each hold one reference to some object) with the given names.

In exchange for the lost flexibility, you gain a lot of bytes per instance (probably meaningful only if you have zillions of instances gallivanting around, but, there are use cases for that).

Rosewater answered 7/10, 2009 at 1:47 Comment(5)
This explains how the mechanism is implemented but does not explain why is it implemented in this way. I can think of at least two or three ways to implement adding dict on the fly which will not have the overhead downside but will add some simplicity.Planoconcave
Note that non-empty __slots__ do not work with variable-length types such as str, tuple, and in Python 3 also int.Highcolored
Nice and thanks! If an object doesn't have __dict__, does its class must have a __slot__ attribute?Alyworth
It's a great explanation, but still doesn't answer why (or how) Sub has the __dict__ attribute and object doesn't, being that Sub inherit from object, how is that attribute (and others like __module__) added in the inheritance? May be this could be a new questionForrester
An object's __dict__ is only created the first time it's needed, so the memory cost situation isn't quite as simple as the asizeof output makes it look. (asizeof doesn't know how to avoid __dict__ materialization.) You can see the dict not getting materialized until needed in this example, and you can see one of the code paths responsible for __dict__ materialization here.Especial
K
18

As other answerers have said, an object does not have a __dict__. object is the base class of all types, including int or str. Thus whatever is provided by object will be a burden to them as well. Even something as simple as an optional __dict__ would need an extra pointer for each value; this would waste additional 4-8 bytes of memory for each object in the system, for a very limited utility.


Instead of doing an instance of a dummy class, in Python 3.3+, you can (and should) use types.SimpleNamespace for this.

Kaciekacy answered 9/4, 2014 at 15:9 Comment(0)
A
4

It is simply due to optimization.

Dicts are relatively large.

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

Most (maybe all) classes that are defined in C do not have a dict for optimization.

If you look at the source code you will see that there are many checks to see if the object has a dict or not.

Antilles answered 7/10, 2009 at 2:45 Comment(0)
T
2

So, investigating my own question, I discovered this about the Python language: you can inherit from things like int, and you see the same behaviour:

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

I assume the error at the end is because the add function returns an int, so I'd have to override functions like __add__ and such in order to retain my custom attributes. But this all now makes sense to me (I think), when I think of "object" like "int".

Trapezius answered 7/10, 2009 at 1:16 Comment(0)
P
1

https://docs.python.org/3/library/functions.html#object :

Note: object does not have a __dict__, so you can’t assign arbitrary attributes to an instance of the object class.

Parsley answered 18/7, 2020 at 12:44 Comment(0)
G
0

It's because object is a "type", not a class. In general, all classes that are defined in C extensions (like all the built in datatypes, and stuff like numpy arrays) do not allow addition of arbitrary attributes.

Grosso answered 7/10, 2009 at 1:16 Comment(3)
But object() is an object, just like Sub() is an object. My understanding is that both s and o are objects. So what is the fundamental difference between s and o? Is it that one is an instantiated type and the other is an instantiated class?Trapezius
Bingo. That's exactly the issue.Grosso
In Python 3, the difference between types and classes doesn't really exist. So "type" & "class" are now fairly synonymous. But you still can't add attributes to those classes that don't have a __dict__, for the reasons given by Alex Martelli.Whitford
S
-2

This is (IMO) one of the fundamental limitations with Python - you can't re-open classes. I believe the actual problem, though, is caused by the fact that classes implemented in C can't be modified at runtime... subclasses can, but not the base classes.

Simplehearted answered 7/10, 2009 at 1:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.