Altering the Behavior of Inherited Methods


You've seen how you can extend a class by adding new methods to a derived class. But you can also redefine how an inherited method of a base class works in a derived class. This is known as overriding the method. When you override a base class method, you have two choices. You can create a method with completely new functionality, or you can incorporate the functionality of the base class method that you're overriding.

As an example, take your Drag_Racer class again. Let's say that its stop() method simply applies the racer's brakes. If you want to create a new drag racer class that can stop even more quickly (by releasing a parachute behind the racer), you could derive a new, Parachute_Racer class from Drag_Racer and override its stop() method. You could write the new stop() method so that it invokes the stop() method of the original Drag_Racer class (which applies the racer's brakes) and then defines the action of the racer releasing a parachute.

Introducing the Playing Cards 3.0 Program

The Playing Cards 3.0 program derives two new classes of playing cards from the Card class you've been working with. The first new class defines cards that can't be printed. More precisely, when you print an object of this class, the text <unprintable> is displayed. The next class defines cards that can be either face up or face down. When you print an object of this class, there are two possible results. If the card is face up, it prints out just like an object of the Card class. But if the card is face down, the text XX is displayed. Figure 9.6 shows a sample run of the program.

click to expand
Figure 9.6: By overriding the inherited __str__() method, objects of different derived classes are printed out differently.

Creating a Base Class

To derive a new class, you need to start with a base class. For this program, I use the same Card class you've come to know and love:

 # Playing Cards 3.0 # Demonstrates inheritance - overriding methods # Michael Dawson 4/16/03 class Card(object):     """ A playing card. """     RANKS = ["A", "2", "3", "4", "5", "6", "7",               "8", "9", "10", "J", "Q", "K"]     SUITS = ["c", "d", "h", "s"]     def __init__(self, rank, suit):         self.rank = rank         self.suit = suit     def __str__(self):         rep = self.rank + self.suit         return rep 

Overriding Base Class Methods

Next, I derive a new class for unprintable cards based on Card. The class header looks pretty standard:

 class Unprintable_Card(Card): 

From this header, you know that Unprintable_Card inherits all of the methods of Card. But I can change the behavior of an inherited method by defining it in a derived class. And that's just what I did in the remainder of the method definition:

    """ A Card that won't reveal its rank or suit when printed. """    def __str__(self):        return "<unprintable>" 

The Unprintable_Card class inherits the __str__() method from Card. But I also define a new __str__() method in Unprintable_Card that overrides (or replaces) the inherited one. Any time you create a method in a derived class with the same name as an inherited method, you override the inherited method in the new class. So, when you print an Unprintable_Card object, the text <unprintable> is displayed.

A derived class has no effect on a base class. A base class doesn't care if you derive a new class from it, or if you override an inherited method in the new class. The base class still functions as it always has. This means that when you print a Card object, it will appear as it always does.

Invoking Base Class Methods

Sometimes when you override the method of a base class, you want to incorporate the inherited method's functionality. For example, I want to create a new type of playing card class based on Card. I want an object of this new class to have an attribute that indicates whether or not the card is face up. This means I need to override the inherited constructor method from Card with a new constructor that creates a face up attribute. However, I also want my new constructor to create and set rank and suit attributes, just like the Card constructor already does. Instead of retyping the code from the Card constructor, I could invoke it from inside my new constructor. Then, it would take care of creating and initializing rank and suit attributes for an object of my new class. Back in the constructor method of my new class, I could add the attribute that indicates whether or not the card is face up. Well, that's exactly the approach I take in the Positionable_Card class:

 class Positionable_Card(Card):     """ A Card that can be face up or face down. """     def __init__(self, rank, suit, face_up = True):         super(Positionable_Card, self).__init__(rank, suit)         self.is_face_up = face_up 

The new function in the constructor, super(), lets you invoke the method of a base class (also called a superclass). The line super(Positionable_Card, self).__init__(rank, suit) invokes the __init__() method of Card (the superclass of Positionable_Card). The first argument in this function call, Positionable_Card, says that I want to invoke a method of the superclass (or base class) of Positionable_Card, which is Card. The next argument, self, passes a reference to the object so that Card can get at the object to add the rank and suit attributes to it. The next part of the statement, __init__(rank, suit), tells Python that I want to invoke the constructor method of Card and I want to pass it the values of rank and suit.

TRAP

The super() function was introduced in Python 2.2 and only works with new-style classes. If you're using old-style classes, you can still invoke a base class method, you just have to explicitly specify the name of the class. For example, if I want to explicitly invoke the constructor of the Card class in Positionable_Card, I could use this line:

           Card.__init__(self, rank, suit) 

But the super() function is much better in more complex situations, so use super() whenever possible over this explicit way of calling a base class method.

The next method in Positionable_Card also overrides a method inherited from Card and invokes the overridden method:

    def __str__(self):        if self.is_face_up:            rep = super(Positionable_Card, self).__str__()        else:            rep = "XX"        return rep 

This __str__() method first checks to see if an object's face_up attribute is True (which means that the card is face up). If so, the string representation for the card is set to the string returned from Card's __str__() method called with the Positionable_Card object. In other words, if the card is face up, the card prints out like any object of the Card class. However, if the card is not face up, the string representation returned is "XX".

The last method in the class doesn't override an inherited method. It simply extends the definition of this new class:

     def flip(self):         self.is_face_up = not self.is_face_up 

The method flips a card over by toggling the value of an object's face_up attribute. If an object's face_up attribute is True, then invoking the object's flip() method sets the attribute to False. If an object's face_up attribute is False, then invoking the object's flip() method sets the attribute to True.

Using the Derived Classes

In the main part of the program, I create three objects: one from Card, another from Unprintable_Card, and the last from Positionable_Card:

 #main card1 = Card("A", "c") card2 = Unprintable_Card("A", "d") card3 = Positionable_Card("A", "h") 

Next, I print the Card object:

 print "Printing a Card object:" print card1 

This works just like in previous programs, and the text Ac is displayed.

The next thing I do is print an Unprintable_Card object:

 print "\nPrinting an Unprintable_Card object:" print card2 

Even though the object has a rank attribute set to "A" and a suit attribute set to "d", printing the object displays the text <unprintable> because the Unprintable_Card class overrides its inherited __str__() method with one that always returns the string "<unprintable>".

The next two lines print a Positionable_Card object:

 print "\nPrinting a Positionable_Card object:" print card3 

Since the object's face_up attribute is True, the object's __str__() method invokes Card's __str__() method and the text Ah is displayed.

Next, I invoke the Positionable_Card object's flip() method:

 print "Flipping the Positionable_Card object." card3.flip() 

As a result, the object's face_up attribute is set to False.

The next two lines print the Positionable_Card object again:

 print "Printing the Positionable_Card object:" print card3 raw_input("\n\nPress the enter key to exit.") 

This time the second line displays the text XX because the object's face_up attribute is False.




Python Programming for the Absolute Beginner
Python Programming for the Absolute Beginner, 3rd Edition
ISBN: 1435455002
EAN: 2147483647
Year: 2003
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net