All attributes of a class are indeed stored in a dictionary. Note that methods are just attributes too; they just happen to be callable. Python uses dictionaries in a lot of places; the global namespace of a module is a dictionary too, for example. It is usually the job of the object.__getattribute__
hook to translate attribute access into a key lookup in the attribute dictionary.
The body of a class statement is executed like a function, and the resulting local namespace (the dictionary produced by calling locals()
) is then handed the metaclass to produce the class object. That's what attrs
is bound to in you example code.
See the class
statement reference documentation:
The class’s suite is then executed in a new execution frame (see [Naming and binding](The class’s suite is then executed in a new execution frame (see Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains mostly function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary.
Bold emphasis mine.
You can find more detail in the Metaclasses section of the datamodel documentation; quoting the Creating the class object section:
Once the class namespace has been populated by executing the class body, the class object is created by calling metaclass(name, bases, namespace, **kwds)
The namespace dictionary is then attached to the newly-created class object by type.__new__()
.
The datamodel documentation covers this as well, under the Custom classes section:
A class has a namespace implemented by a dictionary object. Class attribute references are translated to lookups in this dictionary, e.g., C.x
is translated to C.__dict__["x"]
(although there are a number of hooks which allow for other means of locating attributes).
You may want to read up on descriptors as well; methods are created by binding functions found in the class namespace to the instance on which you looked up the method name.