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

Making a frozen hand from a mutable hand

If we want to perform statistical analysis of specific Hand instances, we might want to create a dictionary that maps a Hand instance to a count. We can't use a mutable Hand class as the key in a mapping. We can, however, parallel the design of set and frozenset and create two classes: Hand and FrozenHand. This allows us to "freeze" a Hand class via FrozenHand; the frozen version is immutable and can be used as a key in a dictionary.

The following is a simple Hand definition:

class Hand:
     def __init__( self, dealer_card, *cards ):
        self.dealer_card= dealer_card
        self.cards= list(cards)
    def __str__( self ):
        return ", ".join( map(str, self.cards) )
    def __repr__( self ):
        return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format(
        __class__=self.__class__,
        _cards_str=", ".join( map(repr, self.cards) ),
        **self.__dict__ )
    def __eq__( self, other ):
        return self.cards == other.cards and self.dealer_card == other.dealer_card
    __hash__ = None

This is a mutable object (__hash__ is None) that has a proper equality test that compares two hands.

The following is a frozen version of Hand:

import sys
class FrozenHand( Hand ):
    def __init__( self, *args, **kw ):
        if len(args) == 1 and isinstance(args[0], Hand):
            # Clone a hand
            other= args[0]
            self.dealer_card= other.dealer_card
            self.cards= other.cards
        else:
            # Build a fresh hand
            super().__init__( *args, **kw )
    def __hash__( self ):
        h= 0
        for c in self.cards:
            h = (h + hash(c)) % sys.hash_info.modulus
        return h

The frozen version has a constructor that will build one Hand class from another Hand class. It defines a __hash__() method that sums the card's hash value that is limited to the sys.hash_info.modulus value. For the most part, this kind of modulus-based calculation works out reasonably well for computing hashes of composite objects.

We can now use these classes for operations such as the following code snippet:

stats = defaultdict(int)

d= Deck()
h = Hand( d.pop(), d.pop(), d.pop() )
h_f = FrozenHand( h )
stats[h_f] += 1

We've initialized a statistics dictionary, stats, as a defaultdict dictionary that can collect integer counts. We could also use a collections.Counter object for this.

By freezing a Hand class, we can use it as a key in a dictionary, collecting counts of each hand that actually gets dealt.