8. Inheritance¶
In this chapter we look at a larger example using object oriented programming and learn about the very useful OOP feature of inheritance.
8.1. Composition¶
By now, you have seen several examples of composition. One of the first
examples was using a method invocation as part of an expression. Another
example is the nested structure of statements; you can put an if
statement
within a while
loop, within another if
statement, and so on.
Having seen this pattern, and having learned about lists and objects, you should not be surprised to learn that you can create lists of objects. You can also create objects that contain lists (as attributes); you can create lists that contain lists; you can create objects that contain objects; and so on.
In this chapter we will look at some examples of these combinations, using
Card
objects as an example.
8.2. Card
objects¶
If you are not familiar with common playing cards, now would be a good time to get a deck, or else this chapter might not make much sense. There are fifty-two cards in a deck, each of which belongs to one of four suits and one of thirteen ranks. The suits are Spades, Hearts, Diamonds, and Clubs (in descending order in bridge). The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen, and King. Depending on the game that you are playing, the rank of Ace may be higher than King or lower than 2.
If we want to define a new object to represent a playing card, it is obvious
what the attributes should be: rank
and suit
. It is not as obvious what
type the attributes should be. One possibility is to use strings containing
words like "Spade"
for suits and "Queen"
for ranks. One problem with
this implementation is that it would not be easy to compare cards to see which
had a higher rank or suit.
An alternative is to use integers to encode the ranks and suits. By encode, we do not mean what some people think, which is to encrypt or translate into a secret code. What a computer scientist means by encode is to define a mapping between a sequence of numbers and the items I want to represent. For example:
Spades --> 3
Hearts --> 2
Diamonds --> 1
Clubs --> 0
An obvious feature of this mapping is that the suits map to integers in order, so we can compare suits by comparing integers. The mapping for ranks is fairly obvious; each of the numerical ranks maps to the corresponding integer, and for face cards:
Jack --> 11
Queen --> 12
King --> 13
The reason we are using mathematical notation for these mappings is that they
are not part of the Python program. They are part of the program design, but
they never appear explicitly in the code. The class definition for the Card
type looks like this:
class Card:
def __init__(self, suit=0, rank=0):
self.suit = suit
self.rank = rank
As usual, we provide an initialization method that takes an optional parameter for each attribute.
To create an object that represents the 3 of Clubs, use this command:
three_of_clubs = Card(0, 3)
The first argument, 0
, represents the suit Clubs.
8.3. Class attributes and the __str__
method¶
In order to print Card
objects in a way that people can easily read, we
want to map the integer codes onto words. A natural way to do that is with
lists of strings. We assign these lists to class attributes at the top of
the class definition:
class Card:
SUITS = ('Clubs', 'Diamonds', 'Hearts', 'Spades')
RANKS = ('narf', 'Ace', '2', '3', '4', '5', '6', '7',
'8', '9', '10', 'Jack', 'Queen', 'King']
def __init__(self, suit=0, rank=0):
self.suit = suit
self.rank = rank
def __str__(self):
"""
>>> print(Card(2, 11))
Queen of Hearts
"""
return '{0} of {1}'.format(Card.RANKS[self.rank],
Card.SUITS[self.suit])
if __name__ == '__main__':
import doctest
doctest.testmod()
Class attributes like Card.SUITS
and Card.RANKS
are defined outside of
any method, and can be accessed from any of the methods in the class.
Inside __str__
, we can use SUITS
and RANKS
to map the numerical
values of suit
and rank
to strings. For example, the expression
Card.SUITS[self.suit]
means use the attribute suit
from the object
self
as an index into the class attribute named SUITS
, and select the
appropriate string.
The reason for the "narf"
in the first element in ranks
is to act as a
place keeper for the zero-eth element of the list, which will never be used.
The only valid ranks are 1 to 13. This wasted item is not entirely necessary.
We could have started at 0, as usual, but it is less confusing to encode 2 as
2, 3 as 3, and so on.
We have a doctest in the __str__
method to confirm that Card(2, 11)
will display as “Queen of Hearts”.
8.4. Comparing cards¶
For primitive types, there are conditional operators ( <
, >
, ==
,
etc.) that compare values and determine when one is greater than, less than, or
equal to another. For user-defined types, we can override the behavior of the
built-in operators by providing a method named __cmp__
. By convention,
__cmp__
takes two parameters, self
and other
, and returns 1 if the
first object is greater, -1 if the second object is greater, and 0 if they are
equal to each other.
Some types are completely ordered, which means that you can compare any two elements and tell which is bigger. For example, the integers and the floating-point numbers are completely ordered. Some sets are unordered, which means that there is no meaningful way to say that one element is bigger than another. For example, the fruits are unordered, which is why you cannot compare apples and oranges.
The set of playing cards is partially ordered, which means that sometimes you can compare cards and sometimes not. For example, you know that the 3 of Clubs is higher than the 2 of Clubs, and the 3 of Diamonds is higher than the 3 of Clubs. But which is better, the 3 of Clubs or the 2 of Diamonds? One has a higher rank, but the other has a higher suit.
In order to make cards comparable, you have to decide which is more important, rank or suit. To be honest, the choice is arbitrary. For the sake of choosing, we will say that suit is more important, because a new deck of cards comes sorted with all the Clubs together, followed by all the Diamonds, and so on.
With that decided, we can write __cmp__
:
def __cmp__(self, other):
# check the suits
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# suits are the same... check ranks
if self.rank > other.rank: return 1
if self.rank < other.rank: return -1
# ranks are the same... it's a tie
return 0
In this ordering, Aces appear lower than Deuces (2s).
8.5. Decks¶
Now that we have objects to represent Card
s, the next logical step is to
define a class to represent a Deck
. Of course, a deck is made up of cards,
so each Deck
object will contain a list of cards as an attribute.
The following is a class definition for the Deck
class. The initialization
method creates the attribute cards
and generates the standard set of
fifty-two cards:
class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
The easiest way to populate the deck is with a nested loop. The outer loop
enumerates the suits from 0 to 3. The inner loop enumerates the ranks from 1 to
13. Since the outer loop iterates four times, and the inner loop iterates
thirteen times, the total number of times the body is executed is fifty-two
(thirteen times four). Each iteration creates a new instance of Card
with
the current suit and rank, and appends that card to the cards
list.
The append
method works on lists but not, of course, tuples.
8.6. Printing the deck¶
As usual, when we define a new type of object we want a method that prints the
contents of an object. To print a Deck
, we traverse the list and print each
Card
:
class Deck:
...
def print_deck(self):
for card in self.cards:
print(card)
Here, and from now on, the ellipsis ( ...
) indicates that we have omitted
the other methods in the class.
As an alternative to print_deck
, we could write a __str__
method for
the Deck
class. The advantage of __str__
is that it is more flexible.
Rather than just printing the contents of the object, it generates a string
representation that other parts of the program can manipulate before printing,
or store for later use.
Here is a version of __str__
that returns a string representation of a
Deck
. To add a bit of pizzazz, it arranges the cards in a cascade where
each card is indented one space more than the previous card:
class Deck:
...
def __str__(self):
s = ""
for i in range(len(self.cards)):
s += " " * i + str(self.cards[i]) + "\n"
return s
This example demonstrates several features. First, instead of traversing
self.cards
and assigning each card to a variable, we are using i
as a
loop variable and an index into the list of cards.
Second, we are using the string multiplication operator to indent each card by
one more space than the last. The expression " " * i
yields a number of
spaces equal to the current value of i
.
Third, instead of using the print
function to print the cards, we use the
str
function. Passing an object as an argument to str
is equivalent to
invoking the __str__
method on the object.
Finally, we are using the variable s
as an accumulator. Initially,
s
is the empty string. Each time through the loop, a new string is
generated and concatenated with the old value of s
to get the new value.
When the loop ends, s
contains the complete string representation of the
Deck
, which looks like this:
>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
4 of Clubs
5 of Clubs
6 of Clubs
7 of Clubs
8 of Clubs
9 of Clubs
10 of Clubs
Jack of Clubs
Queen of Clubs
King of Clubs
Ace of Diamonds
And so on. Even though the result appears on 52 lines, it is one long string that contains newlines.
8.7. Shuffling the deck¶
If a deck is perfectly shuffled, then any card is equally likely to appear anywhere in the deck, and any location in the deck is equally likely to contain any card.
To shuffle the deck, we will use the randrange
function from the random
module. With two integer arguments, a
and b
, randrange
chooses a
random integer in the range a <= x < b
. Since the upper bound is strictly
less than b
, we can use the length of a list as the second parameter, and
we are guaranteed to get a legal index. For example, this expression chooses
the index of a random card in a deck:
random.randrange(0, len(self.cards))
An easy way to shuffle the deck is by traversing the cards and swapping each card with a randomly chosen one. It is possible that the card will be swapped with itself, but that is fine. In fact, if we precluded that possibility, the order of the cards would be less than entirely random:
class Deck:
...
def shuffle(self):
import random
num_cards = len(self.cards)
for i in range(num_cards):
j = random.randrange(i, num_cards)
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
Rather than assume that there are fifty-two cards in the deck, we get the
actual length of the list and store it in num_cards
.
For each card in the deck, we choose a random card from among the cards that
haven’t been shuffled yet. Then we swap the current card ( i
) with the
selected card ( j
). To swap the cards we use a tuple assignment:
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
8.8. Removing and dealing cards¶
Another method that would be useful for the Deck
class is remove
,
which takes a card as a parameter, removes it, and returns True
if
the card was in the deck and False
otherwise:
class Deck:
...
def remove(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
The in
operator returns True
if the first operand is in the second,
which must be a list or a tuple. If the first operand is an object, Python uses
the object’s __cmp__
method to determine equality with items in the list.
Since the __cmp__
in the Card
class checks for deep equality, the
remove
method checks for deep equality.
To deal cards, we want to remove and return the top card. The list method
pop
provides a convenient way to do that:
class Deck:
...
def pop(self):
return self.cards.pop()
Actually, pop
removes the last card in the list, so we are in effect
dealing from the bottom of the deck.
One more operation that we are likely to want is the boolean function
is_empty
, which returns true if the deck contains no cards:
class Deck:
...
def is_empty(self):
return (len(self.cards) == 0)
8.9. Inheritance¶
The language feature most often associated with object-oriented programming is inheritance. Inheritance is the ability to define a new class that is a modified version of an existing class.
The primary advantage of this feature is that you can add new methods to a class without modifying the existing class. It is called inheritance because the new class inherits all of the methods of the existing class. Extending this metaphor, the existing class is sometimes called the parent class. The new class may be called the child class or sometimes subclass.
Inheritance is a powerful feature. Some programs that would be complicated without inheritance can be written concisely and simply with it. Also, inheritance can facilitate code reuse, since you can customize the behavior of parent classes without having to modify them. In some cases, the inheritance structure reflects the natural structure of the problem, which makes the program easier to understand.
On the other hand, inheritance can make programs difficult to read. When a method is invoked, it is sometimes not clear where to find its definition. The relevant code may be scattered among several modules. Also, many of the things that can be done using inheritance can be done as elegantly (or more so) without it. If the natural structure of the problem does not lend itself to inheritance, this style of programming can do more harm than good.
In this chapter we will demonstrate the use of inheritance as part of a program that plays the card game Old Maid. One of our goals is to write code that could be reused to implement other card games.
8.10. A hand of cards¶
For almost any card game, we need to represent a hand of cards. A hand is similar to a deck, of course. Both are made up of a set of cards, and both require operations like adding and removing cards. Also, we might like the ability to shuffle both decks and hands.
A hand is also different from a deck. Depending on the game being played, we might want to perform some operations on hands that don’t make sense for a deck. For example, in poker we might classify a hand (straight, flush, etc.) or compare it with another hand. In bridge, we might want to compute a score for a hand in order to make a bid.
This situation suggests the use of inheritance. If Hand
is a subclass of
Deck
, it will have all the methods of Deck
, and new methods can be
added.
In the class definition, the name of the parent class appears in parentheses:
class Hand(Deck):
pass
This statement indicates that the new Hand
class inherits from the existing
Deck
class.
The Hand
constructor initializes the attributes for the hand, which are
name
and cards
. The string name
identifies this hand, probably by
the name of the player that holds it. The name is an optional parameter with
the empty string as a default value. cards
is the list of cards in the
hand, initialized to the empty list:
class Hand(Deck):
def __init__(self, name=""):
self.cards = []
self.name = name
For just about any card game, it is necessary to add and remove cards from the
deck. Removing cards is already taken care of, since Hand
inherits
remove
from Deck
. But we have to write add
:
class Hand(Deck):
...
def add(self,card):
self.cards.append(card)
Again, the ellipsis indicates that we have omitted other methods. The list
append
method adds the new card to the end of the list of cards.
8.11. Dealing cards¶
Now that we have a Hand
class, we want to deal cards from the Deck
into
hands. It is not immediately obvious whether this method should go in the
Hand
class or in the Deck
class, but since it operates on a single deck
and (possibly) several hands, it is more natural to put it in Deck
.
deal
should be fairly general, since different games will have different
requirements. We may want to deal out the entire deck at once or add one card
to each hand.
deal
takes two parameters, a list (or tuple) of hands and the total number
of cards to deal. If there are not enough cards in the deck, the method deals
out all of the cards and stops:
class Deck :
...
def deal(self, hands, num_cards=999):
num_hands = len(hands)
for i in range(num_cards):
if self.is_empty(): break # break if out of cards
card = self.pop() # take the top card
hand = hands[i % num_hands] # whose turn is next?
hand.add(card) # add the card to the hand
The second parameter, num_cards
, is optional; the default is a large
number, which effectively means that all of the cards in the deck will get
dealt.
The loop variable i
goes from 0 to nCards-1
. Each time through the
loop, a card is removed from the deck using the list method pop
, which
removes and returns the last item in the list.
The modulus operator ( %
) allows us to deal cards in a round robin (one
card at a time to each hand). When i
is equal to the number of hands in the
list, the expression i % nHands
wraps around to the beginning of the list
(index 0).
8.12. Printing a Hand¶
To print the contents of a hand, we can take advantage of the printDeck
and
__str__
methods inherited from Deck
. For example:
>>> deck = Deck()
>>> deck.shuffle()
>>> hand = Hand("frank")
>>> deck.deal([hand], 5)
>>> print(hand)
Hand frank contains
2 of Spades
3 of Spades
4 of Spades
Ace of Hearts
9 of Clubs
It’s not a great hand, but it has the makings of a straight flush.
Although it is convenient to inherit the existing methods, there is additional
information in a Hand
object we might want to include when we print one. To
do that, we can provide a __str__
method in the Hand
class that
overrides the one in the Deck
class:
class Hand(Deck)
...
def __str__(self):
s = "Hand " + self.name
if self.is_empty():
s = s + " is empty\n"
else:
s = s + " contains\n"
return s + Deck.__str__(self)
Initially, s
is a string that identifies the hand. If the hand is empty,
the program appends the words is empty
and returns s
.
Otherwise, the program appends the word contains
and the string
representation of the Deck
, computed by invoking the __str__
method in
the Deck
class on self
.
It may seem odd to send self
, which refers to the current Hand
, to a
Deck
method, until you remember that a Hand
is a kind of Deck
.
Hand
objects can do everything Deck
objects can, so it is legal to send
a Hand
to a Deck
method.
In general, it is always legal to use an instance of a subclass in place of an instance of a parent class.
8.13. The CardGame
class¶
The CardGame
class takes care of some basic chores common to all games,
such as creating the deck and shuffling it:
class CardGame:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
This is the first case we have seen where the initialization method performs a significant computation, beyond initializing attributes.
To implement specific games, we can inherit from CardGame
and add features
for the new game. As an example, we’ll write a simulation of Old Maid.
The object of Old Maid is to get rid of cards in your hand. You do this by matching cards by rank and color. For example, the 4 of Clubs matches the 4 of Spades since both suits are black. The Jack of Hearts matches the Jack of Diamonds since both are red.
To begin the game, the Queen of Clubs is removed from the deck so that the Queen of Spades has no match. The fifty-one remaining cards are dealt to the players in a round robin. After the deal, all players match and discard as many cards as possible.
When no more matches can be made, play begins. In turn, each player picks a card (without looking) from the closest neighbor to the left who still has cards. If the chosen card matches a card in the player’s hand, the pair is removed. Otherwise, the card is added to the player’s hand. Eventually all possible matches are made, leaving only the Queen of Spades in the loser’s hand.
In our computer simulation of the game, the computer plays all hands. Unfortunately, some nuances of the real game are lost. In a real game, the player with the Old Maid goes to some effort to get their neighbor to pick that card, by displaying it a little more prominently, or perhaps failing to display it more prominently, or even failing to fail to display that card more prominently. The computer simply picks a neighbor’s card at random.
8.14. OldMaidHand
class¶
A hand for playing Old Maid requires some abilities beyond the general
abilities of a Hand
. We will define a new class, OldMaidHand
, that
inherits from Hand
and provides an additional method called
remove_matches
:
class OldMaidHand(Hand):
def remove_matches(self):
count = 0
original_cards = self.cards[:]
for card in original_cards:
match = Card(3 - card.suit, card.rank)
if match in self.cards:
self.cards.remove(card)
self.cards.remove(match)
print("Hand {0}: {1} matches {2}".format(self.name, card, match)
count = count + 1
return count
We start by making a copy of the list of cards, so that we can traverse the
copy while removing cards from the original. Since self.cards
is modified
in the loop, we don’t want to use it to control the traversal. Python can get
quite confused if it is traversing a list that is changing!
For each card in the hand, we figure out what the matching card is and go
looking for it. The match card has the same rank and the other suit of the same
color. The expression 3 - card.suit
turns a Club (suit 0) into a Spade
(suit 3) and a Diamond (suit 1) into a Heart (suit 2). You should satisfy
yourself that the opposite operations also work. If the match card is also in
the hand, both cards are removed.
The following example demonstrates how to use remove_matches
:
>>> game = CardGame()
>>> hand = OldMaidHand("frank")
>>> game.deck.deal([hand], 13)
>>> print(hand)
Hand frank contains
Ace of Spades
2 of Diamonds
7 of Spades
8 of Clubs
6 of Hearts
8 of Spades
7 of Clubs
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
10 of Diamonds
10 of Hearts
>>> hand.remove_matches()
Hand frank: 7 of Spades matches 7 of Clubs
Hand frank: 8 of Spades matches 8 of Clubs
Hand frank: 10 of Diamonds matches 10 of Hearts
>>> print(hand)
Hand frank contains
Ace of Spades
2 of Diamonds
6 of Hearts
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
Notice that there is no __init__
method for the OldMaidHand
class. We
inherit it from Hand
.
8.15. OldMaidGame
class¶
Now we can turn our attention to the game itself. OldMaidGame
is a subclass
of CardGame
with a new method called play
that takes a list of players
as a parameter.
Since __init__
is inherited from CardGame
, a new OldMaidGame
object
contains a new shuffled deck:
class OldMaidGame(CardGame):
def play(self, names):
# remove Queen of Clubs
self.deck.remove(Card(0,12))
# make a hand for each player
self.hands = []
for name in names:
self.hands.append(OldMaidHand(name))
# deal the cards
self.deck.deal(self.hands)
print("---------- Cards have been dealt")
self.printHands()
# remove initial matches
matches = self.removeAllMatches()
print("---------- Matches discarded, play begins")
self.printHands()
# play until all 50 cards are matched
turn = 0
numHands = len(self.hands)
while matches < 25:
matches = matches + self.playOneTurn(turn)
turn = (turn + 1) % numHands
print("---------- Game is Over")
self.printHands()
The writing of printHands()
is left as an exercise.
Some of the steps of the game have been separated into methods.
remove_all_matches
traverses the list of hands and invokes
remove_matches
on each:
class OldMaidGame(CardGame):
...
def remove_all_matches(self):
count = 0
for hand in self.hands:
count = count + hand.remove_matches()
return count
count
is an accumulator that adds up the number of matches in each hand and
returns the total.
When the total number of matches reaches twenty-five, fifty cards have been removed from the hands, which means that only one card is left and the game is over.
The variable turn
keeps track of which player’s turn it is. It starts at 0
and increases by one each time; when it reaches numHands
, the modulus
operator wraps it back around to 0.
The method playOneTurn
takes a parameter that indicates whose turn it is.
The return value is the number of matches made during this turn:
class OldMaidGame(CardGame):
...
def play_one_turn(self, i):
if self.hands[i].is_empty():
return 0
neighbor = self.find_neighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].add(pickedCard)
print("Hand", self.hands[i].name, "picked", pickedCard)
count = self.hands[i].remove_matches()
self.hands[i].shuffle()
return count
If a player’s hand is empty, that player is out of the game, so he or she does nothing and returns 0.
Otherwise, a turn consists of finding the first player on the left that has cards, taking one card from the neighbor, and checking for matches. Before returning, the cards in the hand are shuffled so that the next player’s choice is random.
The method find_neighbor
starts with the player to the immediate left and
continues around the circle until it finds a player that still has cards:
class OldMaidGame(CardGame):
...
def find_neighbor(self, i):
numHands = len(self.hands)
for next in range(1,numHands):
neighbor = (i + next) % numHands
if not self.hands[neighbor].is_empty():
return neighbor
If find_neighbor
ever went all the way around the circle without finding
cards, it would return None
and cause an error elsewhere in the program.
Fortunately, we can prove that that will never happen (as long as the end of
the game is detected correctly).
We have omitted the print_hands
method. You can write that one yourself.
The following output is from a truncated form of the game where only the top fifteen cards (tens and higher) were dealt to three players. With this small deck, play stops after seven matches instead of twenty-five.
>>> import cards
>>> game = cards.OldMaidGame()
>>> game.play(["Allen","Jeff","Chris"])
---------- Cards have been dealt
Hand Allen contains
King of Hearts
Jack of Clubs
Queen of Spades
King of Spades
10 of Diamonds
Hand Jeff contains
Queen of Hearts
Jack of Spades
Jack of Hearts
King of Diamonds
Queen of Diamonds
Hand Chris contains
Jack of Diamonds
King of Clubs
10 of Spades
10 of Hearts
10 of Clubs
Hand Jeff: Queen of Hearts matches Queen of Diamonds
Hand Chris: 10 of Spades matches 10 of Clubs
---------- Matches discarded, play begins
Hand Allen contains
King of Hearts
Jack of Clubs
Queen of Spades
King of Spades
10 of Diamonds
Hand Jeff contains
Jack of Spades
Jack of Hearts
King of Diamonds
Hand Chris contains
Jack of Diamonds
King of Clubs
10 of Hearts
Hand Allen picked King of Diamonds
Hand Allen: King of Hearts matches King of Diamonds
Hand Jeff picked 10 of Hearts
Hand Chris picked Jack of Clubs
Hand Allen picked Jack of Hearts
Hand Jeff picked Jack of Diamonds
Hand Chris picked Queen of Spades
Hand Allen picked Jack of Diamonds
Hand Allen: Jack of Hearts matches Jack of Diamonds
Hand Jeff picked King of Clubs
Hand Chris picked King of Spades
Hand Allen picked 10 of Hearts
Hand Allen: 10 of Diamonds matches 10 of Hearts
Hand Jeff picked Queen of Spades
Hand Chris picked Jack of Spades
Hand Chris: Jack of Clubs matches Jack of Spades
Hand Jeff picked King of Spades
Hand Jeff: King of Clubs matches King of Spades
---------- Game is Over
Hand Allen is empty
Hand Jeff contains
Queen of Spades
Hand Chris is empty
So Jeff loses.
8.16. Glossary¶
- encode¶
To represent one set of values using another set of values by constructing a mapping between them.
- class attribute¶
A variable that is defined inside a class definition but outside any method. Class attributes are accessible from any method in the class and are shared by all instances of the class.
- accumulator¶
A variable used in a loop to accumulate a series of values, such as by concatenating them onto a string or adding them to a running sum.
- inheritance¶
The ability to define a new class that is a modified version of a previously defined class.
- parent class¶
The class from which a child class inherits.
- child class¶
A new class created by inheriting from an existing class; also called a subclass.