Home Blog Enums: The Good, the Bad, and the Ugly

DEV

Enums: The Good, the Bad, and the Ugly

Posted by alex on July 21, 2017, 9:30 a.m.

Enums have been in Python since version 3.4. They're not the newest kid on the block, but many 3rd party libraries (Django included) still don't natively support them. For this reason I have tended to use dictionary objects to emulate enums. A "fake" enum might look something like this:

1
2
3
4
5
colors = {
    'RED': 1,
    'BLUE': 2,
    'GREEN': 3,
}

This has the advantage of being a builtin Python object. It's supported everywhere. It's also clear and unambiguous. You wont accidentally try to compare 'blue' to 'BLUE' or 'Blue'. Someone else who reads your code and sees colors['BLUE'] can immediately go to the definition and see all the possible values. Many people used (and still use!) this pattern to create enums because of its convenience.

This dictionary-based approach to enums has many weaknesses, however. When you dereference colors['BLUE'], you get an integer, and integers support operations that simply don't make sense with enums. What does it mean to multiply RED by two? According to your dictionary enum, RED * 2 means BLUE. You can also subtract two from GREEN to get RED.

Furthermore, and perhaps most dangerously, let's say you have another dictionary enum of, say, fruit:

1
2
3
4
5
fruit = {
    'BANANA': 1,
    'APPLE': 2,
    'GRAPEFRUIT': 3,
}

Now colors['BLUE'] will compare as equal to fruit['APPLE']. In my perfect world, such a comparison would throw a TypeError, because you're trying to compare two different types (typing in Python is an entirely separate blog post). It should at least never return True.

These problems are all solved by the Enum module. After you've defined an Enum, even if you use two identical sets of values for two different Enum definitions, a comparison between them will always return False. You also can't multiply RED by two anymore to get BLUE.

You're also going to have a lot of trouble getting it to work in any large project, because, as mentioned before, Enums don't have very good support in many other Python packages.

  • Django doesn't natively support EnumFields in models.
  • Django Rest Framework doesn't natively support EnumFields in serializers.
  • The json library doesn't support serializing and deserializing Enum objects.

In the end, I don't think there is a clear winner in terms of the better data structure to use in your programs. Both have their advantages and disadvantages. A program that uses a builtin like dict for its enumerations will be much easier to get working with most 3rd party libraries, and a program that uses the enum module is likely to be more correct the first time. but even with enum, Python correctness is hard to prove statically anyway. You've got tests, right?