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.