Follow Techotopia on Twitter

On-line Guides
All Guides
eBook Store
iOS / Android
Linux for Beginners
Office Productivity
Linux Installation
Linux Security
Linux Utilities
Linux Virtualization
Linux Kernel
System/Network Admin
Programming
Scripting Languages
Development Tools
Web Development
GUI Toolkits/Desktop
Databases
Mail Systems
openSolaris
Eclipse Documentation
Techotopia.com
Virtuatopia.com

How To Guides
Virtualization
General System Admin
Linux Security
Linux Filesystems
Web Servers
Graphics & Desktop
PC Hardware
Windows
Problem Solutions

  




 

 

Chapter 22. Advanced Class Definition

This section builds up some additional class definition techniques. We describe the basics of inheritance in the section called “Inheritance”. We turn to a specific inheritance technique, polymorphism in the section called “Polymorphism”. There are some class-related functions, which we describe in the section called “Built-in Functions”. We'll look at some specific class initializer technique in the section called “Initializer Techniques”. We include a digression on design approaches in the section called “Design Approaches”. In the section called “Class Variables” we provide information on class-level variables, different from instance variables. We conclude this chapter with some style notes in the section called “Style Notes”.

Inheritance

One of the four important features of class definition is inheritance. You can create a subclass which inherits all of the features of a superclass. The subclass can add or replace method functions of the superclass. This is typically used by defining a general-purpose superclass and creating specialized subclasses that all inherit the general-purpose features but add special-purposes features of their own.

We do this by specifying the parent class when we create a subclass.

class subclass ( superclass ): suite

All of the methods of the superclass are, by definition, also part of the subclass. Often the suite of method functions will add to or override the definition of a parent method.

If we omit providing a superclass, we create a classical class definition, where the Python type is instance; we have to do additional processing to determine the actual type. When we use object as the superclass, the Python type is reported more simply as the appropriate class object. As a general principle, every class definition should be a subclass of object, either directly or indirectly.

Extending a Class. There are two trivial subclassing techniques. One defines a subclass which adds new methods to the superclass. The other overrides a superclass method. The overriding technique leads to two classes which are polymorphic because they have the same interface. We'll return to polymorphism in the section called “Polymorphism”.

Here's a revised version of our basic Dice class and a subclass to create CrapsDice.

Example 22.1. crapsdice.py

#!/usr/bin/env python
"""Define a Die, Dice and CrapsDice."""

class Die( object ):

See the definition in Example 21.1, “die.py”.


class Dice( object ):
    """Simulate a pair of dice."""
    def __init__( self ):
        "Create the two Die objects."
        self.myDice = ( Die(), Die() )
    def roll( self ):
        "Return a random roll of the dice."
        for d in self.myDice:
            d.roll()
    def getTotal( self ):
        "Return the total of two dice."
        return self.myDice[0].value + self.myDice[1].value
    def getTuple( self ):
        "Return a tuple of the dice."
        return self.myDice

class CrapsDice( Dice ):
    """Extends Dice to add features specific to Craps."""
    def hardways( self ):
        """Returns True if this was a hardways roll?"""
        return self.myDice[0].value == self.myDice[1].value
    def isPoint( self, value ):
        """Returns True if this roll has the given total"""
        return self.getTotal() == value

The CrapsDice class contains all the features of Dice as well as the additional features we added in the class declaration. We can write applications which create a CrapsDice instance. We can, for example, evaluate the roll and hardways methods of CrapsDice. The roll method is inherited from Dice, but the hardways method is a direct part of CrapsDice.

Adding Instance Variables. Adding new instance variables requires that we extend the __init__ method. In this case we want an __init__ function that starts out doing everything the superclass __init__ function does, and then creates a few more attributes. Python provides us the super function to help us do this. We can use super to distinguish between method functions with the same name defined in the superclass and extended in a subclass.

super( type , variable )

This will do two things: locate the superclass of the given type, and it will assure that the given variable is an appropriate object of the superclass. This is often used to call a superclass method from within a subclass: super( MyClass ,self). aMethod ( args ).

Here's a template that shows how a subclass __init__ method uses super to evaluate the superclass __init__ method.

class Subclass( Superclass ):
    def __init__( self ):
        super(Subclass,self)__init__()
        
Subclass-specific stuff

This will provide the original self variable to parent class method function so that it gets the superclass initialization. After that, we can add our subclass initialization.

We'll look at additional techniques for creating very flexible __init__ methods in the section called “Initializer Techniques”.

Various Kinds of Cards. Let's look clisely at the problem of cards in Blackjack. All cards have several general features: they have a rank and a suit. All cards have a point value. However, some cards use their rank for point value, other cards use 10 for their point value and the aces can be either 1 or 11, depending on the the rest of the cards in the hand. We looked at this in the the section called “Playing Cards and Decks” exercise in Chapter 21, Classes .

We can model this very accurately by creating a Card class that encapsulates the generic features of rank, suit and point value. Our class will have instance variables for these attribites. The class will also have two functions to return the hard value and soft value of this card. In the case of ordinary non-face, non-ace cards, the point value is always the rank. We can use this Card class for the number cards, which are most common.

class Card( object ):
    """A standard playing card for Blackjack."""
    def __init__( self, r, s ):
        self.rank, self.suit = r, s
        self.pval= r
    def __str__( self ):
        return "%2d%s" % ( self.rank, self.suit )
    def getHardValue( self ):
        return self.pval
    def getSoftValue( self ):
        return self.pval

We can create a subclass of Card which is specialized to handle the face cards. This subclass simply overrides the value of self.pval, using 10 instead of the rank value. In this case we want a FaceCard.__init__ method that uses the parent's Card.__init__ method, and then does additional processing. The existing definitions of getHardValue and getSoftValue method functions, however, work fine for this subclass. Since Card is a subclass of object, so is FaceCard.

Additionally, we'd like to report the card ranks using letters (J, Q, K) instead of numbers. We can override the __str__ method function to do this translation from rank to label.

class FaceCard( Card ):
    """A 10-point face card: J, Q, K."""
    def __init__( self, r, s ):
        super(FaceCard,self).__init__( r, s )
        self.pval= 10
    def __str__( self ):
        label= ("J","Q","K")[self.rank-11]
        return "%2s%s" % ( label, self.suit )

We can also create a subclass of Card for Aces. This subclass inherits the parent class __init__ function, since the work done there is suitable for aces. The Ace class, however, provides a more complex algorithms for the getHardValue and getSoftValue method functions. The hard value is 1, the soft value is 11.

class Ace( Card ):
    """An Ace: either 1 or 11 points."""
    def __str__( self ):
        return "%2s%s" % ( "A", self.suit )
    def getHardValue( self ):
        return 1
    def getSoftValue( self ):
        return 11

Deck and Shoe as Collections of Cards. In a casino, we can see cards handled in a number of different kinds of collections. Dealers will work with a single deck of 52 cards or a multi-deck container called a shoe. We can also see the dealer putting cards on the table for the various player's hands, as well as a dealer's hand.

Each of these collections has some common features, but each also has unique features. Sometimes it's difficult to reason about the various classes and discern the common features. In these cases, it's easier to define a few classes and then refactor the common features to create a superclass with elements that have been removed from the subclasses. We'll do that with Decks and Shoes.

We can define a Deck as a sequence of Cards. The __init__ method function of Deck creates appropriate Cards of each subclass; Card objects in the range 2 to 10, FaceCard obejcts with ranks of 11 to 13, and Ace objects with a rank of 1.

class Deck( object ):
    """A deck of cards."""
    def __init__( self ):
        self.cards= []
        for suit in ( "C", "D", "H", "S" ):
            self.cards+= [Card(r,suit) for r in range(2,11)]
            self.cards+= [TenCard(r,suit) for r in range(11,14)]
            self.cards+= [Ace(1,suit)]
    def deal( self ):
        for c in self.cards:
            yield c

In this example, we created a single instance variable self.cards within each Deck instance. For dealing cards, we've provided a generator function which yields the Cards in a random order. We've omitted the randomization from the deal function; we'll return to it in the exercises.

For each suit, we created the Cards of that suit in three steps.

  1. We created the number cards with a list comprehension to generate all ranks in the range 2 through 10.

  2. We created the face cards with a similar process, except we use the TenCard class constructor, since blackjack face cards all count as having ten points.

  3. Finally, we created a singleton list of an Ace instance for the given suit.

We can use Deck objects to create an multi-deck shoe; a shoe is what dealers use in casinos to handle several decks of slippery playing cards. The Shoe class will create six separate decks, and then merge all 312 cards into a single sequence.

class Shoe( object ):
    """Model a multi-deck shoe of cards."""
    def __init__( self, decks=6 ):
        self.cards= []
        for i in range(decks):
            d= Deck()
            self.cards += d.cards
    def deal( self ):
        for c in self.cards:
            yield c

For dealing cards, we've provided a generator function which yields the Cards in a random order. We've omitted the randomization from the deal function; we'll return to it in the exercises.

Factoring Out Common Features. When we compare Deck and Shoe, we see two obviously common features: they both have a collection of Cards, called self.cards; they both have a deal method which yields the set of cards.

We also see things which are different. The most obvious differences are details of initializing self.cards. It turns out that the usual procedure for dealing from a shoe involves shuffling all of the cards, but dealing from only four or five of the six available decks. This is done by inserting a marker one or two decks in from the end of the shoe.

In factoring out the common features, we have a number of strategies.

  • One of our existing classes is already generic-enough to be the superclass. In the Card example, we used the generic Card class as superclass for other cards as well as the class used to implement the number cards. In this case we will make concrete object instances from the superclass.

  • We may need to create a superclass out of our subclasses. Often, the superclass isn't useful by itself; only the subclasses are really suitable for making concrete object instances. In this case, the superclass is really just an abstraction, it isn't meant to be used by itself.

Here's an abstract CardDealer from which we can subclass Deck and Shoe. Note that it does not create any cards. Each subclass must do that. Similarly, it can't deal properly because it doesn't have a proper shuffle method defined.

class CardDealer( object ):
    def __init__( self ):
        self.cards= []
    def deal( self ):
        for c in self.shuffle():
            yield c
    def shuffle( self ):
        return NotImplemented

Python does not have a formal notation for abstract or concrete superclasses. When creating an abstract superclass it is common to return NotImplemented or raise NotImplementedError to indicate that a method must be overridden by a subclass.

We can now rewrite Deck as subclasses of CardDealer.

class Deck( CardDealer ):
    def __init__( self ):
        super(Deck,self).__init__()
        for s in ("C","D","H","S"):
            
Build the three varieties of cards


    def shuffle( self ):
        
Randomize all cards, return all cards

We can also rewrite Shoe as subclasses of CardDealer.

class Shoe( CardDealer ):
    def __init__( self, decks=6 ):
        CardDealer.__init__( self )
        for i in range(decks):
            d= Deck()
            self.cards += d.cards

    def shuffle( self ):
        
Randomize all cards, return a subset of the cards

The benefit of this is to assure that Deck and Shoe actually share common features. This is not "cut and paste" sharing. This is "by definition" sharing. A change to CardDealer will change both Deck and Shoe, assuring complete consistency.


 
 
  Published under the terms of the Open Publication License Design by Interspire