While working on a piece of code, I ran into an unexpected bug- in my program not Python. I’ve since done some investigating and I’ve learned something about how Python deals with class attributes and instance attributes. Following is a little exposition of what I’ve learned.
Consider the following class:
class foo():
bar = ''
baz = 1
bam = ''
Now bar
, baz
and bam
are all class attributes. Any instance of class foo
will have those attributes available. So, for example:
i = foo()
print i.baz
will result in printing 1. Ok, no big deal. Seems straight forward.
Now, say we wrote some code that relied on iterating over the attributes of an instance object. Python makes this, seemingly, simple- that’s what the __dict__
method is for. As I’ll show, however, it’s also where things get interesting. The results from the following print statements yield surprising results(at least to the uninitiated):
print foo.__dict__
{'baz': 1, '__module__': '__main__', 'bam': '', 'bar': '', '__doc__': None}
print i.__dict__
{}
Huh? Instance i
has no attributes? But that’s not possible! We just printed one of them (i.baz
) up above! What the heck is going on?
In a nutshell, an instance object has it’s own namespace and the __dict__
method prints it. When Python does an attribute lookup, it first looks in the instance name space, then the class namespace. If we want to see the class attributes through the instance, then we have to specify the proper namespace, like so:
print i.__class__.__dict__
{'baz': 1, '__module__': '__main__', 'bam': '', 'bar': '', '__doc__': None}
Ahhh. There we go.
Once this concept is grokked, the following code snippets will not be surprising in the least. For instance, let’s change the value of baz
to 2:
i.baz = 2
print i.baz
2
Seems reasonable. But let’s now print the instance namespace:
print i.__dict__
{'baz': 2}
Hmmm. Now we’ve got an instance attribute named baz
and its value is 2. But we’ve still got a class attribute named baz
and guess what it’s value is:
print i.__class__.baz
1
So when we “changed” the value of baz
, rather than actually changing it, Python created a completely new attribute and because of it’s attribute lookup rules, when we print it out we see the instance attribute. But the class attribute of the same name still exists and has the original value.
So where am I going with all of this? Well, one thing I’ve been trying to figure out is the intended uses of some of this stuff. For example, I could also have created class foo
as follows:
class foo()
def __init__(self):
self.bar = ''
self.baz = 1
self.bam = ''
What’s the difference between this and the earlier definition? Specifically, bar
, baz
and bam
are all instance attributes now as opposed to class attributes. So this means we’ll get a different result when printing out the namespace:
i = foo()
print i.__dict__
{'baz': 1, 'bam': '', 'bar': ''}
So now I’ve got something of a guideline for when to use the __init__
method for classes and attribute creation versus making a class attribute. If the attribute is supposed to basically function as a constant, make it a class attribute. If the attribute is going to be subject to modification, create an instance attribute via the __init__
method. Of course, it’s possible to modify class attributes by accessing them through the __class__
namespace, but the code doesn’t look as nice:
i.__class__.baz = 2
Python seems to be quite rigid in it’s application of namespace rules. Even from within the object itself! If we had created a method to, seemingly, modify the attribute values, we’d still have to be sure to specify the correct namespace using the __class__
designation. If we just used the self.baz = new_value
construct, python simply creates an instance attribute named baz
with value new_value
. The class attribute remains unchanged. To accomplish the desired effect the assignment has to be of the form self.__class__.baz = new_value
.
There are some further subtle surprises if the attributes happen to be lists or dicts. But I need to investigate those a little more thoroughly before I have anything to say about them.