Programming Python, Part II

Having covered some advanced features in Part I, it's time to include some basics.

The short introduction to object-oriented programming (OOP) in Part I of this article left out a big topic—inheritance. This feature is what makes OOP really useful, and as OOP tries to mimic real life, I explain inheritance here with real-life examples.

Think about a chair. A chair is made out of some kind of material, has two armrests, a back, a color, a style and maybe even a warranty. Now, think about a table. It is made out of some kind of material, might have some drawers, a color, a style and maybe a warranty. They have a lot in common! If we were to make the two classes, Chair and Table, a lot of code would be repeated. In programming, when you write the same line of code twice, you probably are doing something wrong—inheritance to the rescue.

A chair is a piece of furniture. So is a table. Such similarities can be in the Furniture class. Let's make the Furniture class have a default material and the ability to set other materials:

class Furniture(object):
    def __init__(self):
        self._material = "wood"
    def set_material(self, material):
        self._material = material

And now, a Chair class inheriting Furniture:

class Chair(Furniture):
    def __init__(self):
        self._backrest_height = 30
    def set_backrest_height(self, height):
        self._backrest_height = height

Now, you know what goes inside parentheses in the class header: the name of the class being inherited, which also is known as a super class or parent class. Let's play a bit with this, so you can see what happens:

>>> c = Chair()
>>> c.set_backrest_height(50)
>>> c._backrest_height
>>> c.set_material("plastic")
>>> c._material

As you can see, the methods of Furniture also are on Chair. I leave the definition of the Table class as an exercise for the reader. But first, here's another interaction:

>>> d = Chair()
>>> d._backrest_height
>>> d._material
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: 'Chair' object has no attribute '_material'

I bet that is not what you expected. Let's take a closer look at what happened. We created a Chair, the method Chair.__init__ was run setting _backrest_height. Oh! Nobody called Furniture.__init__, which never set _material. There are two solutions to that.

Setting _material in Chair.__init__ is not a solution. If we do that, the classes would be coupled, meaning the implementation of one will depend on the implementation of the other. If we change the name of _material to _materials, suddenly Chair will stop working. If you have hundreds of classes developed by hundreds of different people, keeping track of those changes is difficult. Also, Furniture will grow to have more members, so we have to remember to set all those members to the same defaults in Chair.__init__. I'm getting a headache just thinking about it.

One real solution is calling Furniture.__init__ and rewriting Chair.__init__ this way:

def __init__(self):
    self._backrest_height = 30

We had to pass self to __init__, because if we called it with the class instead of the object, it wouldn't know in which object to do its operations.

I personally don't like that solution, because it implies writing the name of the class in two or more places. If you ever change the name, you'll have to remember to run a search and replace. Another solution is more cryptic than it should be, but it doesn't have the problem I just mentioned:

def __init__(self):
    super(Chair, self).__init__()
    self._backrest_height = 30

In this solution, I call super, passing the current class and the current object, and it allows me to make a call to the parent class using the current object. Here we may have a problem if we change the name of the class itself, but running a search and replace on the file is a good idea when making that kind of change. You'd want to change the documentation as well. The real problem with this solution is hard to understand and to explain—it has to do with multiple inheritance. For more information, read “Python's Super Considered Harmful”. Personally, I've been using this second solution without any problems.

You'll see that all classes I defined inherit from object. That is the most basic class—the root (or top) class. It is a good idea to make all your classes inherit from it unless they inherit from another class. If you don't do that, your class will be an old-style class, and some things won't work, such as super. It is important to know this, because you may encounter old-style classes anywhere, and you should be prepared.



Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.


csshef's picture

cool = blog.Post("Cool", "Python is cool")

I can't get the above line to work. Is that correct?


ejournal's picture

Were you using Emacs and following the instructions in part I? If so, the class "Post" will be part of the module "blog". If not, you must arrange for that yourself - the article explains.

If I understand correctly. I'm not a Python programmer. :)