In order to print Card objects in a way that people can easily read, we need a mapping from the integer codes to the corresponding ranks and suits. A natural way to do that is with lists of strings. We assign these lists to class attributes:
# inside class Card: suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades'] rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'Jack', 'Queen', 'King'] def __str__(self): return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])
rank_names, which are defined inside a class but outside of any method, are called class attributes because they are associated with the class object
This term distinguishes them from variables like
rank, which are called instance attributes because they are associated with a particular instance.
Both kinds of attribute are accessed using dot notation. For example, in
self is a Card object, and
self.rank is its rank. Similarly,
Card is a class object, and
Card.rank_names is a list of strings associated with the class.
Every card has its own
rank, but there is only one copy of
Putting it all together, the expression
Card.rank_names[self.rank] means “use the attribute
rank from the object
self as an index into the list
rank_names from the class
Card, and select the appropriate string.”
The first element of
rank_names is None because there is no card with rank zero. By including
None as a place-keeper, we get a mapping with the nice property that the index 2 maps to the string
'2', and so on. To avoid this tweak, we could have used a dictionary instead of a list.
With the methods we have so far, we can create and print cards:
>>> card1 = Card(2, 11) >>> print card1 Jack of Hearts
Figure 18.2.1 is a diagram of the
Card class object and one Card instance.
Card is a class object, so it has type
card1 has type
Card. (To save space, I didn’t draw the contents of