25

I'd like to generate some types at runtime from a config file. For simplity, let's assume I already have the data loaded as a python dictionary:

color_values = dict(RED = 1, YELLOW = 2, GREEN = 3)

How can I transform this into the type (using enum)

class Color(enum.Enum):
    RED = 1
    YELLOW = 2
    GREEN = 3

The following doesn't work

def make_enum(name, values):
    return type(name, (enum.Enum,), values)
>>> Color = make_enum('Color', color_values)
AttributeError: 'dict' object has no attribute '_member_names'
0 _
  • 10,524
  • 11
  • 77
  • 109
Eric
  • 95,302
  • 53
  • 242
  • 374
  • 1
    You should probably use an OrderedDict rather than a regular dict. – user2357112 Nov 15 '17 at 04:04
  • Relevant: https://stackoverflow.com/q/36932/1959808 – 0 _ Nov 15 '17 at 04:08
  • 1
    @IoannisFilippidis: In my defense, I remembered that question as being one pre-`enum.Enum`, which would just tell me to use it. Of course, you're right that it shows the example I needed there. – Eric Nov 15 '17 at 04:10

3 Answers3

43
Color = Enum('Color', color_values)

Tada! There's a provided API for that. You can also give it an iterable of name-value pairs, or an iterable of just names (in which case the values will be auto-filled starting from 1), or a whitespace- or comma-separated string of names (which will also auto-fill values).

user2357112
  • 260,549
  • 28
  • 431
  • 505
1

Here's one super-hacky approach that seems to work:

def make_enum(name, values):
    _k = _v = None
    class TheEnum(enum.Enum):
        nonlocal _k, _v
        for _k, _v in values.items():
            locals()[_k] = _v
    TheEnum.__name__ = name
    return TheEnum

We have to use nonlocal there to stop Enum complaining about the keys k and v being duplicated.

Eric
  • 95,302
  • 53
  • 242
  • 374
-1

And another one that chooses to hit the metaclass internals instead:

def make_enum(name, values):
    meta = type(enum.Enum)
    bases = (enum.Enum,)
    dict = meta.__prepare__(name, bases)
    for k, v in values.items():
        dict[k] = v
    return meta(name, bases, dict)

Or with less __dunder__:

import types
def make_enum(name, values):
    def _update(d1, d2):
        for k, v in d2.items():
            d1[k] = v  # calls __setitem__
    return types.new_class(name, (enum.Enum,), None, lambda ns: _update(ns,values))
Eric
  • 95,302
  • 53
  • 242
  • 374