Categories
Computers Programming Python

Python Class Attributes

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.

Leave a Reply

Your email address will not be published. Required fields are marked *