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

Inheriting definitions for immutable objects

Let's see how the default definitions operate. The following is a simple class hierarchy that uses the default definitions of __hash__() and __eq__():

class Card:
    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__)

class NumberCard( Card ):
    def  __init__( self, rank, suit ):
        super().__init__( str(rank), suit, rank, rank )

class AceCard( Card ):
    def  __init__( self, rank, suit ):
        super().__init__( "A", suit, 1, 11 )

class FaceCard( Card ):
    def  __init__( self, rank, suit ):
        super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )

This is a class hierarchy for philosophically immutable objects. We haven't taken care to implement the special methods that prevent the attributes from getting updated. We'll look at attribute access in the next chapter.

Let's see what happens when we use this class hierarchy:

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

We defined two instances of what appear to be the same Card instance. We can check the id() values as shown in the following code snippet:

>>> print( id(c1), id(c2) )
4302577232 4302576976

They have different id() numbers; they're distinct objects. This meets our expectations.

We can check to see if they're the same using the is operator as shown in the following code snippet:

>>> c1 is c2
False

The "is test" is based on the id() numbers; it shows us that they are indeed separate objects.

We can see that their hash values are different from each other:

>>> print( hash(c1), hash(c2) )
268911077 268911061

These hash values come directly from the id() values. This is our expectation for the inherited methods. In this implementation, we can compute the hash from the id() function as shown in the following code snippet:

>>> id(c1) / 16
268911077.0
>>> id(c2) / 16
268911061.0

As the hash values are different, they must not compare as equal. This fits the definitions of hash and equality. However, this violates our expectations for this class. The following is an equality check:

>>> print( c1 == c2 )
False

We created them with the same arguments. They didn't compare as equal. In some applications, this might not be good. For example, when accumulating statistical counts around dealer cards, we don't want to have six counts for one card because the simulation used a 6-deck shoe.

We can see that they're proper immutable objects as we can put them into a set:

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

This is the documented behavior from the Standard Library Reference documentation. By default, we'll get a __hash__() method based on the ID of the object so that each instance appears unique. However, this isn't always what we want.