Without an in-depth reading of the Python Data Model, there are a few things about attributes in Python that might be surprising:
-
It is possible to add new attributes to any instances of “normal” user-defined classes after instantiation. This is because every class uses a
__dict__
under the hood for keeping track of attributes. -
Assigning to an instance’s class attribute name does not set the value of the class attribute. Instead, it creates a new instance attribute with the same name, which shadows the class attribute
-
The only way (apart from things like C extension modules) to define a class that does not allow to create new attributes after instantiation is to use the
__slots__
mechanism. This results in__dict__
not being used.
Consider the following definition:
class MyClass(object):
a = 1 # class attribute
def __init__(self):
self.b = 1 # instance attribute
inst1 = MyClass()
inst2 = MyClass()
All instances share the same class attribute, so any changes in the attribute value are reflected in all instances:
>>> MyClass.a = 2
>>> inst1.a == inst2.a == 2
True
As a class attribute, a
(in contrast to the instance attribute b
) is not in
inst1.__dict__
:
>>> inst1.__dict__
{'b': 1}
An important feature of instance attributes is that new attributes can be
created not just in the __init__
method, but even after the class is
instantiated. Needless to say, this just shows how important proper unit testing
is for Python development – a simple typo could end up creating a new attribute
instead of changing an existing attribute value.
>>> inst1.new_attr = 0
>>> inst1.__dict__
{'b': 1, 'new_attr': 0}
This, combined with the way Python resolves attribute names, means that the class attribute value can not be set as
>>> inst1.a = 3
Instead of setting the class attribute value, this creates a new instance
attribute a
:
>>> inst1.a
3
>>> inst2.a
2
>>> inst1.__dict__
{'b': 1, 'new_attr': 0, 'a': 3}
It is even possible do delete the instance attribute again, “un-shadowing” the class attribute:
>>> inst1.a
3
>>> del inst1.a
>>> inst1.a
2
There is a gist for a test that explores this more systematically, and
includes a comparison to classes defined with __slots__
(you should
not make a habit of needlessly preventing dynamic attribute creation with
__slots__
!).
As a final note, it is recommended to access class attributes and instance
attributes in the same way whenever possible (i.e. inst1.a
instead of
inst1.__class__.a
or MyClass.a
when working with the instance, and self.a
instead of self.__class__.a
inside methods). This gives the flexibility to
treat class attributes as defaults for instance attributes