3

I started with this code snippet, which, by my understanding, is essentially a class-factory of sorts to emulate an "enum" type from other languages:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

(which I took from here: How can I represent an 'Enum' in Python? )

I get how it works, and it does, but I would like to make my dynamically generated classes, which are of type 'Enum', iterable so that i can do the following in order to do a sanity check:

MyEnum = enum('FOO', 'BAR', 'JIMMY')
def func(my_enum_value):  # expects one of the MyEnum values
    if not my_enum_value in MyEnum:
        raise SomeSortOfException

However, in order to make that sanity check work, I need to make MyEnum iterable. I read here: http://pydanny.blogspot.com/2007/10/required-methods-to-make-class-iterable.html that I needed to add iter len contains and getitem methods to something in order to make it iterable. I started down this road (rewriting the enum code) but got stuck:

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    enums['_enums'] = enums.copy() # (so I'd have them in the Class in order to use for the methods I'll be implementing to make it iterable)
    Enum = type('Enum', (), enums)

    Enum.__iter__ = #Er, how do I do this? Guess I'll ask SO
    Enum.__len__ =  # etc. 
    # . . . 
    return Enum

SO, how do I make my generated Enum classes iterable so I can use the 'in' word with them? This isn't life or death for me, but I'm learning a ton of Python by doing this.

Community
  • 1
  • 1
B Robster
  • 40,605
  • 21
  • 89
  • 122

3 Answers3

5

Use a smarter metaclass than type.

class EnumMC(type):
  def __contains__(self, val):
    return val in self.__dict__

def enum(*sequential, **named):
  enums = dict(zip(sequential, range(len(sequential))), **named)
  return EnumMC('Enum', (), enums)

MyEnum = enum('FOO', 'BAR', 'JIMMY')
def func(my_enum_value):  # expects one of the MyEnum values
    if not my_enum_value in MyEnum:
        raise ValueError()

func('FOO')
func('QUUX')

You may want to use an actual attribute to store the enum keys rather than depending on the class dictionary though.

Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • Thanks, Ignacio. With a few additions, this got me where I needed to be. The metaclass was the key. – B Robster Aug 07 '11 at 05:21
  • 1
    I couldn't resist: (from Nacho Libre) "Ok. Orphans! Listen to Ignacio. I know it is fun to wrestle. A nice piledrive to the face... or a punch to the face... but you cannot do it. Because, it is in the Bible not to wrestle your neighbour." (no insult intended, of course) – B Robster Aug 07 '11 at 05:22
2
class Enum(dict): pass
def enum(*sequential, **named):
    return Enum(zip(sequential, range(len(sequential))), **named)

This is much easier to read, and it supports iteration.

Edit: You still need to do

MyEnum = enum('FOO', 'BAR', 'JIMMY')

then you can do

'FOO' in MyEnum

You could also just use

class Enum(dict):
    def __init__(self, *sequential, **named):
        self.update(zip(sequential, range(len(sequential))))
        self.update(named)
def __getattr__(self, attr):
    if attr in self:
        return self[attr]
    else:
        return super(Enum, self).__getattr__(attr)

to do the same thing.

Edit 2: Another option.

from collections import namedtuple

def enum(*sequential):
    return namedtuple('Enum', sequential)(*sequential)

still call it the same way, and you can do MyEnum.FOO and it supports in. Also edited my above class to support attribute access.

You can also uses range(len(sequential)) to fill in the namedtuple values (not keys) if you want sequential integer values, or use **kwargs and then kwargs.keys() and kwargs.values() if you want to specify arbitrary values.

The last solution is cross-posted to How can I represent an 'Enum' in Python?

Community
  • 1
  • 1
agf
  • 171,228
  • 44
  • 289
  • 238
  • Easier to read: agreed. Supports iteration: false. If you read my original question, I'd like the *class* to support iteration. So I can do the following: 0 in MyEnum – B Robster Aug 07 '11 at 04:47
  • Try it -- `MyEnum = enum('FOO', 'BAR', 'JIMMY')` then `'FOO' in MyEnum`. It works fine. – agf Aug 07 '11 at 04:51
  • Yep. Works as you say it does. It changes the way my "enum" works tho. Wwith the original code, I could do MyEnum.FOO . . . (see the second answer in link above http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python ) rather than MyEnum['FOO'] . I'm not sure thats a bad thing, but i did like the syntactical sugar the original method provided. (And I already sent out an email to my devs saying I added this cool new enum thing to our utils) :) If I'm doing it this way, I might as well just use a dict. – B Robster Aug 07 '11 at 05:00
  • See my last edit if you want them to be attributes -- Both the `dict` subclass and `namedtuple` do that easily. – agf Aug 07 '11 at 05:09
  • Sweet. I particularly like your last answer and will probably end up using it as my implementation. Here's the deal, since my original question asks about how to make a dynamically generated class iterable and Ignacio answered that question, (your solutions avoids the need for dynamically created classes) i marked his as accepted. But Because your solution is more elegant and practical, I'll vote it up, and I think you should post it over at the above link as an answer to the enum question, since its better than those presented there. I'll vote it up there too if you do. Thanks again. – B Robster Aug 07 '11 at 05:32
  • Good suggestion. I added it over there. – agf Aug 07 '11 at 05:51
1

I am not sure why enum need to return a class instead of an instance, so that you can easily implement __contains__, I am also not sure why we even need a enum in python, anyway if you need to add __contains__ at class level you need a metaclass, here it goes

class EnumMeta(type):
    def __contains__(self, x):
        return x in self.__dict__

class EnumBase(object):
    __metaclass__ = EnumMeta

def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (EnumBase,), enums)

MyEnum = enum('FOO', 'BAR', 'JIMMY')

print 'foobar in MyEnum','foobar' in MyEnum
print 'FOO in MyEnum', 'FOO' in MyEnum

output:

foobar in MyEnum False
FOO in MyEnum True
Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • Thanks. Very similar to what Ignacio suggested earlier. As to the need for enum in python. I realize its generally considered not needed: http://www.python.org/dev/peps/pep-0354/ but having one around is handy, especially when devs feel they need one. Mostly I'm learning about metaclasses, and dynamically generated classes and this seems like a good case to learn about it. Very helpful discussion here. – B Robster Aug 07 '11 at 05:35