Chapter 1. New Attribute Access

The Dynamic __dict__

What is an attribute? Quite simply, an attribute is a way to get from one object to another. Apply the power of the almighty dot - objectname.attributename - and voila! you now have the handle to another object. You also have the power to create attributes, by assignment: objectname.attributename = notherobject.

Which object does an attribute access return, though? And where does the object set as an attribute end up? These questions are answered in this chapter.

Example 1.1. Simple attribute access

>>> class C(object):
...     classattr = "attr on class"  1
...
>>> cobj = C()
>>> cobj.instattr = "attr on instance"  2
>>>
>>> cobj.instattr  3
'attr on instance'
>>> cobj.classattr 4
'attr on class'
>>> C.__dict__['classattr'] 5
'attr on class'
>>> cobj.__dict__['instattr'] 6
'attr on instance'
>>>
>>> cobj.__dict__ 7
{'instattr': 'attr on instance'}
>>> C.__dict__ 8
{'classattr': 'attr on class', '__module__': '__main__', '__doc__': None}

1

Attributes can be set on a type.

2

Or even on an instance of a type.

3 4

Both, type and instance attributes are accessible from an instance.

5 6

Attributes really sit inside a dictionary-like __dict__ in the object.

7 8

__dict__ contains only the user-provided attributes (also sometimes called dynamic attributes).

Note that __dict__ is itself an attribute. We didn't set this attribute ourselves, but Python did. Our old friends __class__ and __bases__ (none which appear to be in __dict__ either) also seem to be similar. Let's call them Python-provided attributes. Whether an attribute is Python-provided or not depends on the object in question (__bases__, for example, is Python-provided only for type objects).

We, however, are more interested in user-defined attributes. These are attributes provided by the user, and they usually (but not always) end up in the __dict__ of the object on which they're set.

When accessed (eg.print objectname.attributename), the following objects are searched in sequence for the attribute:

  1. The object itself (objectname.__dict__ or any Python-provided attribute of objectname).

  2. The object's type (objectname.__class__.__dict__). Observe that only __dict__ is searched, which means only user-provided attributes of the type. In other words objectname.__bases__ may not return anything even though objectname.__class__.__bases__ does exist.

  3. The bases of the object's type, their bases, and so on. (__dict__ of each of objectname.__class__.__bases__). More than one base does not confuse Python, and should not concern us at the moment. The point to note is that all bases are searched until an attribute is found.

If all this hunting around fails to find a suitably named attribute, Python raises an AttributeError. The type of the type (objectname.__class__.__class__) is never searched for attribute access on an object (objectname in the example).

The built-in dir() function returns a list of all attributes of an object.

The above section explains the general mechanism for all objects. Even for type objects (for example accessing classname.attrname), with a slight modification: the bases of the type object are searched before the type of the type object (which is classname.__class__, which for most types, by the way, is <type 'type'>).

Some objects, such as built-in types and their instances (lists, tuples, etc.) do not have a __dict__. Consequently user-defined attributes cannot be set on them.

We're not done yet! This was the short version of the story. There is more to what can happen when setting and getting attributes. This is explored in the following sections.