Enum Whoes
By now I’ve learned that when it comes to programming and programming languages, every once in a while you get to a point where something doesn’t work the way you had expected. And occasionally then, after you figure out why it doesn’t do what you expected it to do, you will find that you don’t agree with the sentiment behind it. Case in point… Enum in Python.
I just had a little run-in with the design logic of these and I thought I’d share it with you, and how to get around the problem if you ever have the same issue.
You may recall that I use Enums a lot. My vocabulary is defined with enums and so are many other data values. I am using these because Python has no support for real constants. Don’t ask me, why, but there it is… So, in order to create something that resembles constants, I have been using Enums frequently.
Why would you need constants in the first place? Readability. Instead of having to say theVerb = 125
I honestly prefer to write theVerb = scream
instead. To me, constants promote code clarity, but clearly, the designer of Python disagrees with me on that subject.
So, I started using enums like this at first
class DoorStates ( Enum ): Open = 1 Closed = 2 Locked = 3
This is all nice and well, but the numbering can get a bit tedious, especially if you try to do things out of order and you find yourself renumbering tens of entries. That’s why I found the auto()
function so helpful because it allowed me to change my enum declarations into this
class DoorStates ( Enum ): Open = auto() Closed = auto() Locked = auto()
Now I’m no longer bogged down by numbers and Python is taking care of it for me. Only, it isn’t. At least not the way I thought.
I ran into a problem when I tried to use an enum like an integer, in my case as an index into a list. Using the following declaration, I wanted to use the room names as an index into the theRoom
list that holds all the rooms in my game.
class RoomIDs ( Enum ): Bedroom = auto() GalleryHallway = auto() WailRoom = auto() Foyer = auto() Entrance = auto() Basement = auto()
And that’s when the problems began. Python balked at the usage of code like this
theCurrentRoom = theRooms[ RoomIDs.Bedroom ]
Not cool!
See, when I numbered my enums, I made them integers. auto()
doesn’t really do that. It fills it with an “appropriate value”. That means that it could be anything, from a number to a string literal or an encoded checksum value. Now, I understand that it should be of no concern to me what it is using to uniquely identify each entry, but because of how I approached my enums, I expected them to be integers. (Incidentally, I’ve also been told that other languages do treat enums as integers, so my expectation was not unreasonable at all!)
Okay, so, what to do. I hate working with anonymous numbers that have no meaning, are easily confused or misused. I may be a new programmer but no one can tell that that would be good programming practice.
So, I dug into it and tried to find a solution. Here’s what I soon found. The Python Enum class actually contains an IntEnum
element that, according to the Python documentation, specifically creates an integer constant. Cool! Just the ticket.
I changed the code to this.
class RoomIDs ( IntEnum ): Bedroom = auto() GalleryHallway = auto() WailRoom = auto() Foyer = auto() Entrance = auto() Basement = auto()
…and I found it still didn’t work. Bummer! After another few minutes of research and reading of more documentation, it turns out that auto()
is the culprit.
Remember that part about auto()
creating an appropriate value? Turns out, that it can be anything from an integer to a string and anything in between, despite the fact that I’ve declared the target of the function to be an IntEnum
. Someone tell me how this makes sense, but that’s how it is. As I mentioned, I do not agree with some of the logic behind Python’s language features.
So, what to do? I was not ready to give up just yet. There had to be a way to make this work. Pouring over more documentation and comments in various programming forums, in the end, it turns out that Python can’t do what I want. If you want IntEnum to actually behave like an integer, you will have to assign its value by hand. So, I ended up with this.
class RoomIDs ( IntEnum ): Bedroom = 0 GalleryHallway = 1 WailRoom = 2 Foyer = 3 Entrance = 4 Basement = 5
I still hate the fact that I have to manually number these entries, but at least they are constants and I can use them as indices into a list. It solves my problem, but boy is it ugly.
To make things a bit more secure, if you wish, there is also the @unique
keyword that will check my enum to make sure every value appears only once. This is how it’s used.
@unique class RoomIDs ( IntEnum ): Bedroom = 0 GalleryHallway = 1 WailRoom = 2 Foyer = 3 Entrance = 4 Basement = 5
This is how I am using it currently in my program. It’s still ugly and not really what I wanted, but the unique thing at least prevents that I accidentally assign the same value twice. Ah, the wonders of modern computing… and it’s whoes.