Metaclass Usage in Enum
In the standard library, you’ll find the
enum (small e) module containing the
Enum (capital E) class.
Enum was kind of a late addition to Python, and unlike most languages, where it’s a fundamental data type, Python has used metaclass magic to implement it.
The metaclass in question is called
EnumType. I showed you the
.__new__() hook in a metaclass, but that’s not the only hook. The changes to how metaclasses work that were introduced in Python 3 include the addition of the
It gets called first with the attribute dictionary passed in, and what it returns is what gets passed to
.__new__(). This allows you to make changes to the arguments before they’re processed by
Remember, in Python 3.0, dictionaries weren’t ordered by default. This has some special features to deal with the naming enforced for
Enum member attributes and also gets used internally to store the member attributes themselves.
Fruit is an
Enum is an
EnumType has an
EnumDict. The sample code includes a copy of this module renamed as
py_enum. That way if you want to dig into it yourself, you can do that without having to go find it on your own machine.
I renamed it to avoid namespace conflicts in case you want to actually run it yourself. When I first dug into this, I stuck a breakpoint in pretty much every dunder method in
EnumType to better understand what was going on. All right, let’s go take a look at the actual code.
Because a class that inherits from
Enum, can be inherited from, the first thing done here is to check all the base classes for attributes. It then creates the special
EnumDict class, which is a subclass of dictionary, and it then examines the base classes for information.
Ah, there’s the top. Remember, this is just a single method inside a single metaclass inside the
enum module. After
.__prepare__() has prepared the argument dictionary, it calls
.__new__() on the metaclass.
Simple enums are created when a utility decorator wraps a plain class, turning the class into an
Enum. This case requires special handling, but most of the handling is done in that decorator itself, so
.__new__() only needs to create the class. See?
This allows you to define attributes in the class that don’t participate in the actual
Enum. This can be handy if you’re generating attributes on the fly and you need some values to do your computation, but don’t want them in the end result.
This line defines what you’re not allowed to call them and checks for any intersection with the names you’ve defined in your
Enum. Empty string isn’t allowed, and
mro is a pseudo-keyword that gets used in class hierarchies, and using it as an attribute would break lots of things, so it’s disallowed.
Remember when I spoke about those special sunder values? Well, the next bit of code does some cleanup on them. This same method was called in
.__prepare__(), and it gets information about the inheritance hierarchy.
07:06 There are multiple choices because of inheritance, and it can get even messier if you’re pickling things. I’m not going to go into details here. Just know that the code has to figure out which method is the correct one to assign. Once it’s picked one, it assigns it to a sunder value for later use.
08:00 More flag-handling special code … and finally (well, not so finally, actually), this is where the class is actually created. Phew, it’s a lot, huh? And there’s 120 more lines of code just for special case handling after this.
This is the core of it though. Process the attributes passed in to make sure they’re unique and have allowed names, set up some sunder values, make sure to use the right
.__repr__() methods if you’ve got multi-inheritance going on, and then you’re actually creating the class.
If what is being passed in is a class member itself, then a class member gets returned. It’s sort of the short-circuit response. If what gets passed in is a value like
3, then the dictionary where the values are stored is checked for the corresponding attribute.
Enum class uses the
EnumType as a metaclass to do all the attribute magic, and the
Enum class also overrides the object
.__new__() to handle the attribute instantiation case. Quite the ride, huh?
Become a Member to join the conversation.