94

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?

Piotr Dobrogost
  • 39,915
  • 36
  • 232
  • 353
Smashery
  • 54,058
  • 30
  • 96
  • 124
  • Pure guesswork: The `object` type is immutable and new attributes cannot be added? This seems like it would make the most sense. – Chris Lutz Oct 07 '09 at 01:10
  • 2
    @ S.Lott: See the very first line of this question. Purely curiosity. – Smashery Oct 08 '09 at 04:00
  • 3
    Your title is misleading, you are trying to set attributes on `object` class instances, not on `object` class. – dhill Aug 26 '15 at 10:02

7 Answers7

138

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).

user2357112
  • 235,058
  • 25
  • 372
  • 444
Alex Martelli
  • 811,175
  • 162
  • 1,198
  • 1,373
  • 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. – Георги Кременлиев Apr 03 '14 at 19:20
  • Note that non-empty `__slots__` do not work with variable-length types such as `str`, `tuple`, and in Python 3 also `int`. – arekolek Apr 01 '16 at 09:52
  • Nice and thanks! [If an object doesn't have `__dict__`, does its class must have a `__slot__` attribute?](https://stackoverflow.com/q/46575174/156458) – Tim Oct 04 '17 at 22:37
  • 1
    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 question – Rodrigo E. Principe Oct 26 '17 at 17:21
  • 3
    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](https://ideone.com/xVKqMC), and you can see one of the code paths responsible for `__dict__` materialization [here](https://github.com/python/cpython/blob/v3.7.2/Objects/dictobject.c#L4288). – user2357112 Jan 28 '19 at 07:15
17

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.

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.

Unknown
  • 44,664
  • 26
  • 136
  • 177
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".

Smashery
  • 54,058
  • 30
  • 96
  • 124
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.

AXO
  • 6,987
  • 4
  • 53
  • 56
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.

Ryan
  • 14,540
  • 6
  • 47
  • 50
  • 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? – Smashery Oct 07 '09 at 01:26
  • Bingo. That's exactly the issue. – Ryan Oct 07 '09 at 01:30
  • 1
    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. – PM 2Ring Jul 16 '19 at 16:29
-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.

Peter
  • 121,125
  • 53
  • 174
  • 208