Simplicity using mapping and class objects
In some cases, we can use a mapping instead of a chain of elif
conditions. It's possible to find conditions that are so complex that a chain of elif
conditions is the only sensible way to express them. For simple cases, however, a mapping often works better and can be easy to read.
Since class
is a first-class object, we can easily map from the rank
parameter to the class that must be constructed.
The following is a Card
factory that uses only a mapping:
def card4( rank, suit ): class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard}.get(rank, NumberCard) return class_( rank, suit )
We've mapped the rank
object to a class. Then, we applied the class to the rank
and suit
values to build the final Card
instance.
We can use a defaultdict
class as well. However, it's no simpler for a trivial static mapping. It looks like the following code snippet:
defaultdict( lambda: NumberCard, {1: AceCard, 11: FaceCard, 12: FaceCard, 12: FaceCard} )
Note that the default of a defaultdict
class must be a function of zero arguments. We've used a lambda
construct to create the necessary function wrapper around a constant. This function, however, has a serious deficiency. It lacks the translation from 1
to A
and 13
to K
that we had in previous versions. When we try to add that feature, we run into a problem.
We need to change the mapping to provide both a Card
subclass as well as the string version of the rank
object. What can we do for this two-part mapping? There are four common solutions:
- We can do two parallel mappings. We don't suggest this, but we'll show it to emphasize what's undesirable about it.
- We can map to a two-tuple. This also has some disadvantages.
- We can map to a
partial()
function. Thepartial()
function is a feature of thefunctools
module. - We can also consider modifying our class definition to fit more readily with this kind of mapping. We'll look at this alternative in the next section on pushing
__init__()
into the subclass definitions.
We'll look at each of these with a concrete example.
Two parallel mappings
The following is the essence of the two parallel mappings solution:
class_= {1: AceCard, 11: FaceCard, 12: FaceCard, 13: FaceCard }.get(rank, NumberCard) rank_str= {1:'A', 11:'J', 12:'Q', 13:'K'}.get(rank,str(rank)) return class_( rank_str, suit )
This is not desirable. It involves a repetition of the sequence of the mapping keys 1
, 11
, 12
, and 13
. Repetition is bad because parallel structures never seem to stay that way after the software has been updated.