Giving Meaning to Magic Numbers with Python Enums
1093 words. Time to Read: About 10 minutes.This article is not super in-depth. I just discovered a cool module in the Python Standard Library and wanted to share it to spark your interest. If you run into any issues with it and have any questions, though, I’m happy to try my best to help you work through it!
Using Python’s enum Module
I was working through an exercise on Exercism that had to do with finding the best poker hand, and I found myself with lots of “magic values” floating around. When I say “magic value,” what I mean is a hard-coded constant that is supposed to have some sort of semantic meaning. In this particular case, I had playing cards that could take on one of 13 different specific values, and some of these values had names. And then, I had Poker hands that were getting scored, and those different types of hands also had intrisic values, where some hands were better than others.
My first iteration was extra magicky, and looked something like this:
It felt weird and made my code-spidey-sense tingle. But I let it pass. Then I wrote this code:
More magic numbers! I vaguely remembered that Python had an enum module in its standard library, and I also vaguely remembered that enums were supposed to be good for situations where you’ve got specific categories/types a value can take on. So I did some research. Turns out, they’re actually even cooler than I thought.
The enum module has a bunch of different types of enums, but, because I was doing a lot of sorting, comparing, and relative ranking, as well as going between these and normal numbers, I decided on an IntEnum. This just means that the enum’s values will be integers. But what does that look like?
You can define an Enum with a functional syntax (much like you’d instantiate a class or create a type of NamedTuple), or you can create it with a class syntax. You’ll see both in this article, for different reasons.
I’ll show the functional first, for the poker hand scores. I’m going this route, because I don’t really care what the numbers are. I just want to make the relative ranking very clear.
And that’s all we need. Now we can see their relative values and compare them!
This allows us to semanticize (my new word, thank you!) our code above:
A lot more readable! One way that you know that we’re doing something right is that I was able to delete the explanation comments for some of the less clear conditions without losing any readability at all. Cool, right?
Now, lets look at the other case with the face card values. For this one I used the Class syntax, because I wanted to add a little more functionality. More on that in a minute. Here’s the starting code.
Now we can use it like a regular number, but it has a name!
The inputs were provided as strings of characters: "AH" for Ace of Hearts, "8C" for 8 of Clubs, etc. I started by defining a card like this:
The __init__ method is initializing the instance, and the __lt__ method tells how these objects behave around the < operator. We can now compare them like this:
Even though the value for the king is actually a FaceCard.KING, it compares using its integer value, so I can treat it like a normal number for most things! Enums are cool!
For more info, the standard library docs are really good.
Bonus: Converting Back to Strings!
I thought enum was cool. And then I found out that I needed to convert my card values back to their encoded strings for output. As it turns out, Enums have two main properties: the name, which is like the “key”, and the value. Check this awesomeness:
Now in our Card class:
And because we defined the custom __str__ method for FaceCard the way we did, this code will work no matter whether the card is a face card or not!
Right?? RIGHT?!?! I mean, how elegant can you get?
I want to know how you’ve used enum before. Did you find a neat use case? Are there other gems in the enum module that I haven’t even discovered yet?
Like my stuff? Have questions or feedback for me? Want to mentor me or get my help with something? Get in touch! To stay updated, subscribe via RSS