Before you keep reading...
Runestone Academy can only continue if we get support from individuals like you. As a student you are well aware of the high cost of textbooks. Our mission is to provide great books to you for free, but we ask that you consider a $10 donation, more if you can or less if $10 is a burden.
Before you keep reading...
Making great stuff takes time and $$. If you appreciate the book you are reading now and want to keep quality materials free for other students please consider a donation to Runestone Academy. We ask that you consider a $10 donation, but if you can give more thats great, if $10 is too much for your budget we would be happy with whatever you can afford as a show of support.
22.2. Inheriting Variables and Methods¶
22.2.1. Mechanics of Defining a Subclass¶
We said that inheritance provides us a more elegant way of, for example, creating
Cat types, rather than making a very complex
Pet class. In the abstract, this is pretty intuitive: all pets have certain things, but dogs are different from cats, which are different from birds. Going a step further, a Collie dog is different from a Labrador dog, for example. Inheritance provides us with an easy and elegant way to represent these differences.
Basically, it works by defining a new class, and using a special syntax to show what the new sub-class inherits from a super-class. So if you wanted to define a
Dog class as a special kind of
Pet, you would say that the
Dog type inherits from the
Pet type. In the definition of the inherited class, you only need to specify the methods and instance variables that are different from the parent class (the parent class, or the superclass, is what we may call the class that is inherited from. In the example we’re discussing,
Pet would be the superclass of
Here is an example. Say we want to define a class
Cat that inherits from
Pet. Assume we have the
Pet class that we defined earlier.
We want the
Cat type to be exactly the same as
Pet, except we want the sound cats to start out knowing “meow” instead of “mrrp”, and we want the
Cat class to have its own special method called
chasing_rats, which only
Cat s have.
For reference, here’s the original Tamagotchi code
All we need is the few extra lines at the bottom of the ActiveCode window! The elegance of inheritance allows us to specify just the differences in the new, inherited class. In that extra code, we make sure the
Cat class inherits from the
Pet class. We do that by putting the word Pet in parentheses,
class Cat(Pet):. In the definition of the class
Cat, we only need to define the things that are different from the ones in the
In this case, the only difference is that the class variable
sounds starts out with the string
"Meow" instead of the string
"mrrp", and there is a new method
We can still use all the
Pet methods in the
Cat class, this way. You can call the
__str__ method on an instance of
Cat, the same way you could call it on an instance of
Pet, and the same is true for the
hi method – it’s the same for instances of
Pet. But the
chasing_rats method is special: it’s only usable on
Cat instances, because
Cat is a subclass of
Pet which has that additional method.
In the original Tamagotchi game in the last chapter, you saw code that created instances of the
Pet class. Now let’s write a little bit of code that uses instances of the
Pet class AND instances of the
And you can continue the inheritance tree. We inherited
Pet. Now say we want a subclass of
Cheshire. A Cheshire cat should inherit everything from
Cat, which means it inherits everything that
Cat inherits from
Pet, too. But the
Cheshire class has its own special method,
22.2.2. How the interpreter looks up attributes¶
So what is happening in the Python interpreter when you write programs with classes, subclasses, and instances of both parent classes and subclasses?
This is how the interpreter looks up attributes:
First, it checks for an instance variable or an instance method by the name it’s looking for.
If an instance variable or method by that name is not found, it checks for a class variable. (See the previous chapter for an explanation of the difference between instance variables and class variables.)
If no class variable is found, it looks for a class variable in the parent class.
If no class variable is found, the interpreter looks for a class variable in THAT class’s parent (the “grandparent” class).
This process goes on until the last ancestor is reached, at which point Python will signal an error.
Let’s look at this with respect to some code.
Say you write the lines:
new_cat = Cheshire("Pumpkin") print(new_cat.name)
In the second line, after the instance is created, Python looks for the instance variable
name in the
new_cat instance. In this case, it exists. The name on this instance of
Pumpkin. There you go!
When the following lines of code are written and executed:
cat1 = Cat("Sepia") cat1.hi()
The Python interpreter looks for
hi in the instance of
Cat. It does not find it, because there’s no statement of the form
cat1.hi = .... (Be careful here – if you had set an instance variable on Cat called
hi it would be a bad idea, because you would not be able to use the method that it inherited anymore. We’ll see more about this later.)
Then it looks for hi as a class variable (or method) in the class Cat, and still doesn’t find it.
Next, it looks for a class variable
hi on the parent class of
Pet. It finds that – there’s a method called
hi on the class
Pet. Because of the
hi, the method is invoked. All is well.
However, for the following, it won’t go so well
p1 = Pet("Teddy") p1.chasing_rats()
The Python interpreter looks for an instance variable or method called
chasing_rats on the
Pet class. It doesn’t exist.
Pet has no parent classes, so Python signals an error.
Check your understanding