Which objects in Python can have attributes added dynamically?

Viewed 332

In Python, I can add an attribute to a class C which I have previously defined. However, I cannot add an attribute to list - the resulting error message explains that this is because list is a built-in type:

>>> class C: pass
...
>>> C.foo = 1
>>> C.foo
1

>>> list.foo = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'list'

Similarly, it is possible to add an attribute to an instance of C, but not to an instance of list. In this case, however, the error message is much more vague:

>>> o = C()
>>> o.bar = 2
>>> o.bar
2

>>> o = []
>>> o.bar = 2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'bar'

Why can't I add members to instances of list? Is it, again, because list is a built-in type?

More generally, which objects in Python can have attributes added dynamically?

2 Answers

When setting arbitrary properties on an object, this effectively mutates the object’s __dict__. The __dict__ is a dictionary that stores all members of the object. So in order to add members to an object, there are two conditions:

  1. The object needs to have a __dict__.
  2. The object’s __dict__ needs to be mutable.

There are multiple reasons why one of these conditions may not be true. The following categories of types will usually do not allow object modifications:

  • Built-in native types are implemented in native code and have a fixed set of members, baked into their native object definition. Examples include int, str, list, bytes, … probably most (but not all) built-in types
  • Native types from external libraries, for example numpy stuff.
  • Any Python class that is using __slots__. Slots is an explicit way to turn of the __dict__ and replace it by a fixed set of members instead. So obviously, this will prevent later additions to the object (per design).

What are good ways to detect whether you can modify an object? Well, check conditions from above. Whether the object has a __dict__:

>>> class Example:
        pass
>>> class SlotsExample:
        __slots__ = ['x']

>>> hasattr(Example(), '__dict__')
True
>>> hasattr(SlotsExample(), '__dict__')
False
>>> hasattr(list, '__dict__')
True
>>> hasattr([], '__dict__')
False

And whether the __dict__ is a real dictionary:

>>> isinstance(Example().__dict__, dict)
True
>>> isinstance(list.__dict__, dict)
False

In short, it depends on __setattr__ magic method.

Python attributes are retrieved via __getattr__ and __setattr__. In case you have a class without them, the defaults apply. (Slots are a little different.)

For built-in/extension types (HEAPTYPE in CPython), you can't add any other attributes by default, but you can, however, override it by providing another function. (Impossible with pure Python.)

Related