This is part of the [tracer series](/tag/tracer), so have a look at its [introduction](/post/tracer-tricks) first. ![pause](/media/images/pause.png) 'Member C-style enums? ```c enum A { a, b, c = 10, d, }; ``` What if I told you this is valid Python code: ```python @cenum() class A: a b c = 10 d assert A.a == 0 assert A.b == 1 assert A.c == 10 assert A.d == 11 ``` Crazy, huh? Let's see how it works (and then agree to never, ever use it). ![pause](/media/images/pause.png) Note that ``cenum`` is not really a decorator, but a callable that returns a decorator; that is, we use ``@cenum()``, not ``@cenum``, so we get to run code right before the class is defined. And that's the whole trick: in that code we register a tracer, which is immediately invoked on the class definition, does its thing, and unregisters itself. There's no real overhead, because it only ever happens once... but that brief moment is enough. Here's its skeleton: ```python class cenum(object): def __init__(self): self.old = sys.gettrace() sys.settrace(self.tracer) def tracer(self, frame, event, arg): # Do something with frame. sys.settrace(self.old) def __call__(self, cls): # Do something with cls. return cls ``` Let's go over it again: we create an ``cenum`` instance, and in its ``__init__`` we register ``tracer``. It is immediately invoked on the class definition, gets that class's frame before any evaluation happens, does something to it, and unregisters itself. When the class evaluation is complete, that class's object is passed to ``__call__`` for decoration, and there we do something to it and return it. The only thing left is to, well, actually do something. It's nothing clever, though: the code was *already* valid Python code, except it tried to evaluate (and then discard) the expression ``a``, and ``NameError``ed because ``name 'a' is not defined``. Let's fix that: ```python def tracer(self, frame, event, arg): # Go over all the local variable in the frame, and define them so they don't NameError. self.names = frame.f_code.co_names[2:] # Ignore default attribute names for name in self.names: frame.f_locals[name] = None sys.settrace(self.old) ``` And lo and behold, this code works: ```python @cenum() class A: x y z ``` Except, well, all the attributes are ``None``. That we can fix in the decorator, after the class evaluation is complete: ```python def __call__(self, cls): for n, name in self.names: setattr(cls, name, n) return cls ``` And there you have it: ``A.x == 0``, ``A.y == 1`` and ``A.z == 2``. Magic. ![pause](/media/images/pause.png) A few issues I still have with this code is that it doesn't support "bumping" the enum higher (e.g. ``c = 10``), and that the default attribute names slicing isn't generic across Python versions. Let's fix the bumping first: ```python def __call__(self, cls): n = 0 for name in self.names: value = getattr(cls, name) if isinstance(value, int): n = value setattr(cls, name, n) n += 1 return cls ``` The default attribute names can be filtered out by invoking ``cenum`` on an empty class, and capturing all its attributes as default ones: ```python class cenum(object): default_names = None # ... def tracer(self, frame, event, arg): names = frame.f_code.co_names if self.default_names is None: self.__class__.default_names = names self.names = [name for name in names if name not in self.default_names] for name in self.names: frame.f_locals[name] = None sys.settrace(self.old) # Invoke enum on an empty class to capture the default attribute names. @cenum() class _: pass ``` And that's it! You can get the code on [GitHub](http://github.com/dan-gittik/cenum), and if you're using Python 3, there's actually a better way to do this - so [check it out](/post/enums-revisited)!