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

Overriding definitions for immutable objects

The following is a simple class hierarchy that provides us with definitions of __hash__() and __eq__():

class Card2:
    insure= False
    def __init__( self, rank, suit, hard, soft ):
        self.rank= rank
        self.suit= suit
        self.hard= hard
        self.soft= soft
    def __repr__( self ):
        return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__)
    def __str__( self ):
        return "{rank}{suit}".format(**self.__dict__)
    def __eq__( self, other ):
        return self.suit == other.suit and self.rank == other.rank
    def __hash__( self ):
        return hash(self.suit) ^ hash(self.rank)
class AceCard2( Card2 ):
    insure= True
    def  __init__( self, rank, suit ):
        super().__init__( "A", suit, 1, 11 )

This object is immutable in principle. There's no formal mechanism to make it immutable. We'll look at how to prevent the attribute value changes in Chapter 3, Attribute Access, Properties, and Descriptors.

Also, note that the preceding code omits two of the subclasses that didn't change significantly from the previous example.

The __eq__() method function compares these two essential values: suit and rank. It doesn't compare the hard and soft values; they're derived from rank.

The rules for Blackjack make this definition a bit suspicious. Suit doesn't actually matter in Blackjack. Should we merely compare rank? Should we define an additional method that compares rank only? Or, should we rely on the application to compare ranks properly? There's no best answer to these questions; these are just trade-offs.

The __hash__() method function computes a bit pattern from the two essential values using an exclusive OR of the bits that comprise each value. Using the ^ operator is a quick-and-dirty hash method that often works pretty well. For larger and more complex objects, a more sophisticated hash might be appropriate. Start with ziplib before inventing something that has bugs.

Let's see how objects of these classes behave. We expect them to compare as equal and behave properly with sets and dictionaries. Here are two objects:

>>> c1 = AceCard2( 1, '♣' )
>>> c2 = AceCard2( 1, '♣' )

We defined two instances of what appear to be the same card. We can check the ID values to be sure that they're distinct objects:

>>> print( id(c1), id(c2) )
4302577040 4302577296
>>> print( c1 is c2 )
False

These have different id() numbers. When we test with the is operator, we see that they're distinct.

Let's compare the hash values:

>>> print( hash(c1), hash(c2) )
1259258073890 1259258073890

The hash values are identical. This means that they could be equal.

The equality operator shows us that they properly compare as equal:

>>> print( c1 == c2 )
True

As they're immutable, we can put them into a set as follows:

>>> print( set( [c1, c2] ) )
{AceCard2(suit='♣', rank='A')}

This meets our expectations for complex immutable objects. We had to override both special methods to get consistent, meaningful results.