8
\$\begingroup\$

I'm trying to develop a lightweight enum for Python 2.7. This question is downstream of the SO question here; for context, a streamlined version of the bulleted addendum to that question is replicated below:

My desired feature set is:

  1. Only having to type the enum name (or value) once in the source definition.
  2. Valid enum values exposed for IDE autocomplete.
  3. Enum values stored internally as comprehensible strings.
  4. Straightforward membership testing using native Python in.
  5. Ability to import individual enum subclasses via, say, from .enums import myEnum, to avoid cluttering the local namespace with other enums I don't care about.
  6. I would rather avoid introducing an additional package dependency by using enum34, as I don't (think I) need the feature set.

After reviewing the SO post here on recommendations for non-enum34 implementations of enums in Python 2.7, I also would add:

  1. Clean, readable definition of enum values.

To note, every form of dynamic namespace construction I've tried, in efforts to accomplish #1, has broken #2. I'm pretty sure at this point they're mutually exclusive.

In any event, my current solution goes as follows, defining an enum superclass with a metaclassed type with an iterator that returns all members with string content identical to the respective member names:

class SuperEnum(object):
    class __metaclass__(type):
        def __iter__(self):
            for item in self.__dict__:
                if item == self.__dict__[item]:
                    yield item

Each enum is then subclassed from this SuperEnum, e.g.:

class myEnum(SuperEnum):
    this = 'this'
    that = 'that'
    other = 'other'

The usage is working just as I want (except of course for #1), including for the IDE autocomplete of #2:

>>> myEnum.other
'other'
>>> 'this' in myEnum
True
>>> 'thing' in myEnum
False
>>> e = myEnum.other
>>> e in myEnum
True

This construction is far more concise than anything I'd set up around the time I posted the above-linked SO question, so the need to type the value name twice in the enum class definition isn't terribly bothersome. I'm still interested to know if there's any way to make #1 and #2 play nice together, though.

More generally: what might the weaknesses of this construction be? For my application, performance is of minor concern. There's always the possibility of value collisions, as described here, but using strings as the values dramatically reduces the likelihood. Security issues? Compatibility problems of some kind?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

I've added an answer to your original post at Stack Overflow, and if combined with your code here, you should be able to extend your current solution and thusly achieve #1 and #7 as well.

def new_enum(name, *class_members):                
    """Builds a class <name> with <class_members> having the name as value."""

    return type(name, (SuperEnum, ), { val : val for val in class_members })

myEnum = new_enum('myEnum', 'this', 'that', 'other')

Now the only thing you have to repeat is the class name, but otherwise this should follow all of your guidelines.

Code review

Regarding your coding of SuperEnum you could return a generator object directly if you use the following:

def __iter__(self):
    """Return a generator for enum values.

    When iterating the enum, only return class members whose
    value are identical to the key.
    """ 
    return (item for item in self.__dict__ if item == self.__dict__[item]

And you should add docstrings on how to use your code, and you can surely enhance on my variable names and docstrings.

Major weakness to this implementation is that any string matching your values would actually match the enum. So that if you create two identical enum's they would also match each other. If that is a problem, or an advantage depends on your usage situation. Could possibly be propagated by changing the type of your class members, and then enforced by defining an equality operator on the new type.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks for the input! (1) Absolutely as re documentation; I (perhaps inappropriately?) excised my existing comments. (2) Will the members of the new class be parsed at design time? If not, the enum values will be exposed in an interpreter session but not in the IDE -- this is what happened with all of my prior attempts. (3) Will have to noodle at that last paragraph for a bit; as I haven't delved much into Python's innards, it's not immediately apparent why those two behaviors would result. \$\endgroup\$
    – hBy2Py
    Nov 8, 2015 at 3:40
  • \$\begingroup\$ @Brian, Will it work within a text editor? Most likely, no. But your code example displays the IDLE, and there it does work. It also works in IPython. Does it work in your environment, you tell me. It is dynamic class creation, so you might need some interpreter. \$\endgroup\$
    – holroy
    Nov 8, 2015 at 3:53
5
\$\begingroup\$

An interesting bit of code. :)

The downsides would be the things missing from enum341:

  • printing a variable gives no indication that it's an enum and not a string
  • enums that have the same value will compare equal
  • depending on the value of the member, is won't always work
  • etc.

Plus the surprise other developers will have when your enum doesn't work like the now-standard Python Enum2.

On the bright side, you shouldn't have any problems with pickling.


1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

2 See this answer for the standard Enum usage.

\$\endgroup\$
3
  • \$\begingroup\$ In your third bullet, do you mean is or in? According to this, I shouldn't be (and am not) using is on strings at all. \$\endgroup\$
    – hBy2Py
    Nov 4, 2015 at 13:01
  • \$\begingroup\$ @Brian: I meant is. In the new Enum data type individual members are only created once so Color.blue is Color.blue. In yours, that only works because of certain optimizations -- which means it may not work on different Pythons, and will not work if your strings don't fit the optimization pattern. \$\endgroup\$ Nov 4, 2015 at 14:50
  • \$\begingroup\$ Understood, thanks for the clarification! \$\endgroup\$
    – hBy2Py
    Nov 4, 2015 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.