Up: Classes and Objects

Inheritance

slide 001 Hello, and welcome to the fourth episode of the Software Carpentry lecture on object-oriented programming. In this episode, we’ll show you how to create new classes from old ones.
slide 002 Let’s take another look at the example from our previous episode. We were given a signal that has been sampled at irregular intervals…
slide 003 …and we built classes to do stepwise and linear interpolation in a way that let us use one or the other—or yet another class that we haven’t been written yet—in the rest of our program.
slide 004 A lot of how we implemented those two classes was the same.
slide 005 How can we eliminate the redundancy, so that there’s no duplicated code in our program?
slide 006 Here’s the implementation of StepSignal’s ‘get’ method again. It searches through the (x, y) pairs saved in self.values to find the sample immediately preceding ‘where’, and returns the corresponding y value. If ‘where’ lies outside the sampling range, ‘get’ raises an IndexError exception instead of returning a value.
slide 007 The implementation in LinearSignal is almost identical.
slide 008 In fact, only the line that actually does the interpolation is different; everything else is identical. And as we’ve said before, code that’s duplicated will eventually be wrong.
slide 009 What we want to do is refactor this code so that our methods look like this. The new method, ‘find’, will either return the index of the sample immediately preceding ‘where’, or raise an exception if ‘where’ is out of range. Once we have that index, we can either return the corresponding ‘y’ value (if we’re doing stepwise interpolation) or find a weighted average (if we’re doing linear interpolation).
slide 010 But where should the ‘find’ method go?
slide 011 We don’t want to duplicate it in the two classes—that wouldn’t really count as solving our problem.
slide 012 Instead, we’re going to use inheritance: we’re going to create a new class that inherits all the properties of an existing one, then specialize it.
slide 013 To see how this works, let’s create a class called Parent that has one method, hello. As before, we put the name of Python’s built-in class ‘object’ in parentheses after the name of the new class—we’ll show you why in just a moment.
slide 014 Then let’s create a second class, Child, that defines another method ‘goodbye’.
slide 015 This time, though, we put the name of the class Parent in parentheses when we’re defining Child, rather than the name ‘object’. This tells Python that we want Child to have everything that we defined for Parent, as well as anything new we define specifically for Child.
slide 016 To see what this means, let’s create an object of class Child. As expected, we can then call its ‘goodbye’ method.
slide 017 But look: we can also call its ‘hello’ method, even though we didn’t define ‘hello’ for the class Child. This works because Child inherited the definition of ‘hello’ from Parent: it automatically has everything that Parent defined.
slide 018 Inheritance only works in one direction, though. If we create an object of class Parent, we can call its ‘hello’ method, as shown here.
slide 019 But if we try to call its ‘goodbye’ method, the call fails, because we didn’t define ‘goodbye’ for Parent, and Parent didn’t inherit it from anywhere.
slide 020 This picture shows you what’s going on inside Python in this example.
slide 021 At the top is the class ‘object’, which is built into Python.
slide 022 Below that is our class Parent. It inherits stuff from ‘object’ — that’s why we’ve been putting object’s name in parentheses every time we’ve defined a class so far — and adds a method of its own called ‘hello’.
slide 023 Below that is the class ‘Child’. It inherits from Parent, and adds another method ‘goodbye’.
slide 024 When we create the object ‘c’ of class ‘Child’, Python puts a reference in the object to its class. When we call ‘c.goodbye’, Python follows that reference from ‘c’ to ‘Child’, finds the method, and executes it. When we call ‘c.hello’, Python follows the reference to Child, fails to find ‘hello’, but then sees that ‘Child’ has a reference to another class ‘Parent’. When Python follows that link, it finds that Parent does have the method ‘hello’, so everything’s OK.
slide 025 Similarly, when Python creates the object ‘p’, and then calls ‘p.hello’, it follows p’s link to Parent and finds the method. When we try to call ‘p.goodbye’, though, the interpreter looks in Parent, fails to find anything called ‘goodbye’, then follows the link up to ‘object’, fails again, and raises an exception. There is no reference from Parent to Child, only one from Child to Parent, so there’s no way to get to ‘goodbye’ from the object ‘p’.
slide 026 All right, let’s try to apply this idea to our signal interpolators. Here’s a class called ‘InterpolatedSignal’ that has just one method called ‘find’. Given an ‘x’ value ‘where’, this method searches through ‘self.values’ to find the location of the immediately preceding sample and returns the corresponding index. If ‘where’ is out of bounds, ‘find’ raises an exception.
slide 027 This method isn’t particularly useful on its own, but it is exactly what StepSignal and LinearSignal need.
slide 028 Before we show how they use it, though, there’s a design flaw in this class. ‘find’ depends on ‘self.values’, but InterpolatedSignal doesn’t create this anywhere—it seems to just appear by magic. We’ll come back and fix this in a few slides.
slide 029 Now that we have a way to find where we’re supposed to interpolate, let’s rewrite our actual interpolating classes.
slide 030 Here’s the new version of StepSignal.
slide 031 It’s derived from InterpolatedSignal—again, that’s what it means to put InterpolatedSignal’s name in parentheses in the class definition.
slide 032 which means it can use the ‘find’ method without re-defining it.
slide 033 In order for that to work, though, StepSignal’s constructor has to create self.values, so that it will be there when self.find needs it.
slide 034 This is fragile: there’s nothing in ‘StepSignal’ to tell the next person reading this code why we’re doing this, just as there was nothing in ‘InterpolatedSignal’ to tell someone reading it where ‘self.values’ came from.
slide 035 Dependencies between classes should be more explicit—let’s see how to make them so.
slide 036 In this case, the right solution is to have the parent class store the values we’re using for interpolation.
slide 037 This version of ‘InterpolatedSignal’ does exactly that: its constructor makes a copy of ‘values’ and assigns it to ‘self.values’.
slide 038 It also defines a method called ‘get’ that raised a NotImplementedError exception, so that if anyone ever tries to create an object of this class and use it for interpolation, they’ll get a meaningful error message instead of Python’s default “attribute not found”.
slide 039 We can now rewrite StepSignal’s constructor as shown here.
slide 040 Instead of storing ‘values’ itself, this constructor calls InterpolatedSignal’s constructor, passing in the object being built (that’s ‘self’) and the input parameter ‘values’. The syntax ‘InterpolatedSignal.__init__’ is a bit clumsy, but the effect is pretty simple: StepSignal is asking InterpolatedSignal to do whatever it thinks it has to when it’s creating a new object.
slide 041 And here’s the new LinearSignal. Its constructor is the same—it just asked the parent class to do whatever it needs to do. The beauty of this is that there’s now exactly one place to make a change when we need to. For example, if we want to change the kind of exception that’s raised when someone tries to get a value for a point that’s out of bounds, we change InterpolatedSignal.find and we’re done.
slide 042 Similarly, if we want to check that the samples are actually pairs, and in order (which we should have been doing all along), we can add that code to the parent class, and both of the child classes will automatically get it.
slide 043 One other thing we’ve done here deserves mention. As this diagram shows, we have defined a method called ‘get’ in two places: once in the parent class ‘InterpolatedSignal, and once in the child class ‘StepSignal’. The one in the parent raises an exception every time it’s called, but that’s OK: if we start with an object ‘signal’ of class ‘StepSignal’, and follow the references upward, we find StepSignal’s ‘get’ first and call that, just as we want to. When this happens, we say that the child class is overriding the method in the parent class: it’s defining a method with the same name which takes precedence over the parent’s.
slide 044 Let’s have a closer look at how overriding works.
slide 045 Here’s a class called Parent that defines two methods, hello and goodbye.
slide 046 And here’s a class Child that inherits from Parent and defines its own ‘goodbye’ method.
slide 047 That method overrides the one defined in Parent because it has the same name.
slide 048 Now let’s create an object of class Parent and call its methods. Good—that works as expected.
slide 049 When we create an object of class Child, and call ‘hello’, that also does what we expect—Child didn’t define ‘hello’, so Python looks upward to its parent, finds one there, and uses it.
slide 050 But when we call the Child object’s ‘goodbye’, we get the one defined in Child. The one in the Parent class is still there, but Python finds the one in Child first and uses it.
slide 051

  1. Terri Yu
    March 9th, 2011 at 03:23 | #1

    In Slide 49, there is a typo. The line that reads

    C = child()

    should be

    c = Child()

  2. March 11th, 2011 at 18:41 | #2

    @Terri Yu
    Good catch. I’ve ticketed this.

  1. No trackbacks yet.