Mastering Objectoriented Python
上QQ阅读APP看书,第一时间看更新

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. The partial() function is a feature of the functools 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.