In a [previous post](/post/reclaiming-enums) we saw that we can use tracer tricks to have C-style enums in Python, like so: ```python @cenum() class A: a b c = 10 d assert A.a == 0 assert A.b == 1 assret A.c == 10 assert A.d == 11 ``` But hijacking control and patching frame objects in a tracer feels... wrong. The fun kind of wrong, but still - if there's a better way, we should probably use it instead. And in Python 3, there is: with metaclasses. ![pause](/media/images/pause.png) If you're not sure what a metaclass is, then it's actually pretty simple: it's the class of class, so its ``__init__`` is called whenever a class is created, pretty much like a class's ``__init__`` is called whenever an instance of it is created. Here's an example: ```python >>> class M(type): ... def __init__(cls, name, bases, attrs): ... print('Creating %s' % name) ... >>> class A(metaclass=M): ... pass ... Creating A ``` But in Python ≥ 3.0, metaclasses also have a cool ``__prepare__`` method, which is called before their class body is evaluated in order to generate the dictionary *against* which this body will be evaluated. But... nobody said it has to be a dictionary; or rather, a *normal* dictionary whose behaviour makes sense; sense is overrated, anyway. The problem with this code: ```python class A: a b c ``` Is not that it's invalid - it's that the ``a`` expression ``NameError``s because ``name 'a' is not defined``. In other words, it tries to access the ``a`` key in the *dictionary* that is its namespace, and when it ``KeyError``s, it translates this into a ``NameError``. But seeing as this dictionary can be changed, and seeing as its ``__getitem__`` method can be changed... ```python class CEnumNamespace(dict): def __init__(self): self.index = 0 def __getitem__(self, key): if key not in self: self[key] = self.index self.index += 1 return super().__getitem__(key) ``` Now that's a much nicer dictionary: if a key is missing, it doesn't get mad - it just assigns ``index`` to it, increments ``index``, and returns the result. So ``a`` doesn't ``NameError`` anymore, but get assigned ``0`` instead (so its expression is evaluated successfuly, and although its result is discarded - ``a`` remains in the namespace, and we end up with ``A.a == 0``). The same goes for ``b`` and ``1`` and ``c`` and ``2``. Let's give it a spin. ```python class CEnum(type): def __prepare__(*args): return CEnumNamespace() ``` ```python >>> class A(metaclass=CEnum): ... a ... b ... c ... >>> vars(A) {'a': 1, 'b': 2, 1, 'c': 3, '__name__': 0, ...} ``` Almost got it. Let's filter out keys starting with ``__``, so we skip the default attributes; and also, let's add support for "bumping" (e.g. ``c = 10``): ```python class CEnumNamespace(dict): def __init__(self): self.index = 0 def __getitem__(self, key): if key not in self: self[key] = self.index return super().__getitem__(key) def __setitem__(self, key, value): self[key] = value it isinstance(value, int): self.index = value + 1 ``` Booyah: ```python class A(metaclass=CEnum): a b c = 10 d assert A.a == 0 assert A.b == 1 assert A.c == 10 assret A.d == 11 ``` ![pause](/media/images/pause.png) And... that's it, actually. Hijacking the class-body-namespace-dictionary in ``__prepare__`` is devious, but not as much as patching frame objects in a tracer. [Here's the code](https://github.com/dan-gittik/cenum/tree/master/python3).