Equality for Python

A few days ago in #chipy, the chat room for the Chicago Python Users Group, we had a chat about how Python determines equality. It’s a pretty neat and extensible technique, so I’m going to walk through how I recently used it for playing cards.

Here’s the basic Card class. Note that I’m going to totally skip things like error-checking and documentation to keep the example obvious.

values = (‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘10’, ‘J’, ‘Q’, ‘K’, ‘A’) suits = (‘h’, ‘c’, ‘d’, ‘s’)

class Card: def init(self, value, suit): self.value, self.suit = value, suit

def repr(self): return “” % (self.value, self.card)

Man, does code get short when you don’t bother checking for errors. The usage is pretty clear, but there’s one odd issue:

` »> Card(‘3’, ‘s’) »> Card(‘3’, ‘s’) == Card(‘3’, ‘s’) False `{lang=”python”}

Huh? That’s odd, an instance of Card doesn’t equal another Card just like itself? Well, let’s look at the Python docs. It talks a bit about comparing the builtin types (numbers, strings, lists...) and then says: “Most other types compare unequal unless they are the same object”.

Python does this by comparing the ids of the objects. You can call id() on your objects and see that even identically-constructed objects have different ids because they’re in different locations in memory. This is decent default because you wouldn’t want Python walking deeply through all of your objects, a potentially expensive operation. Python does do a little bit more for equality, as implied by that “most” in the documentation.

Python does one more thing for us. It looks for a function named eq to call on the left-hand object and uses it to determine equality. So let’s add it to Card:

` def eq(self, card): return self.value == card.value and self.suit == card.suit `{lang=”python”}

Easy enough. And usage:

` »> Card(‘3’, ‘s’) == Card(‘3’, ‘s’) True »> Card(‘3’, ‘s’) == Card(‘K’, ‘h’) False »> Card(‘3’, ‘s’) != Card(‘3’, ‘s’) True `{lang=”python”}

Now that last one is a bit surprising. Back at the docs, we learn “There are no implied relationships among the comparison operators. The truth of x==y does not imply that x!=y is false. Accordingly, when defining eq(), one should also define ne() so that the operators will behave as expected.” That’s:

` def ne(self, card): return not self.eq(card) `{lang=”python”}

After that, the Cards equate properly and everything’s happy. One of the best things about Python is that it regularly gives you a sensible default and then lets you customize your code to work seamlessly with the language. This is what us Python fans mean when we go on and on about code being “Pythonic”.