On 31 January 2013 08:32, Terry Reedy <tjreedy@udel.edu> wrote:
On 1/30/2013 10:30 AM, Michael Foord wrote:
On 30 January 2013 15:22, Michael Foord

    With a Python 3 metaclass that provides default values for *looked
    up* entries you could have this:

    class Color(Enum):
         RED, WHITE, BLUE

    The lookup would create the member - with the appropriate value.

class values(dict):
     def __init__(self):
         self.value = 0
     def __getitem__(self, key):


So RED, WHITE, BLUE are 1, 2, 3; not 0, 1, 2 as I and many readers might expect. That aside (which can be fixed), this is very nice.

Here is a version that I think creates an enum with most of the features of traditional and modern enums.

- Enum values are subclasses of int;

- Only need to declare the enum key name;

- Starts at zero by default;

- Can change the start value;

- Can have discontiguous values (e.g. 0, 1, 5, 6);

- Can have other types of class attributes;

- Ensures that there is a 1:1 mapping between key:value (throws an exception if either of these is violated;

- Able to obtain the keys, values and items as per the mapping interface (sorted by value);

- Lookup an enum by key or value;

One thing to note is that *any* class attribute assigned a value which implements __index__ will be considered an enum value assignment.

I've done some funky stuff to ensure that you can access all the above either via the enum class, or by an instance of the enum class. Most of the time you would just use the Enum subclass directly (i.e. it's a namespace) but there may be use cases for having instances of the Enum classes.

import collections
import operator

class EnumValue(int):
    def __new__(cls, key, value):
        e = super().__new__(cls, value)
        super().__setattr__(e, 'key', key)
        return e

    def __setattr__(self, key, value):
        raise TypeError("Cannot set attribute of type %r" % (type(self),))

    def __repr__(self):
        return "<%s '%s': %d>" % (self.__qualname__, self.key, self)

class EnumValues(collections.OrderedDict):
    def __init__(self):
        super().__init__()
        self.value = 0
        self.sealed = False

    def __getitem__(self, key):
        try:
            obj = super().__getitem__(key)

            if not self.sealed and isinstance(obj, EnumValue):
                raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (obj.key, obj, self.value))

            return obj

        except KeyError:
            if key[:2] == '__' and key[-2:] == '__':
                raise

            value = self.value
            super().__setitem__(key, EnumValue(key, value))
            self.value += 1
            return value

    def __setitem__(self, key, value):
        if key[:2] == '__' and key[-2:] == '__':
            return super().__setitem__(key, value)

        try:
            if isinstance(value, EnumValue):
                assert value.key == key
            else:
                value = operator.index(value)
        except TypeError:
            return super().__setitem__(key, value)

        try:
            o = super().__getitem__(key)

            if isinstance(o, EnumValue):
                raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (o.key, o, value))

        except KeyError:
            self.value = value + 1

            if isinstance(value, EnumValue):
                value = value
            else:
                value = EnumValue(key, value)

            super().__setitem__(value.key, value)

class EnumMeta(type):

    @classmethod
    def __prepare__(metacls, name, bases):
        return EnumValues()

    def __new__(cls, name, bases, classdict):
        classdict.sealed = True
        result = type.__new__(cls, name, bases, dict(classdict))
        enum = []

        for v in classdict.values():
            if isinstance(v, EnumValue):
                enum.append(v)

        enum.sort()
        result._key_to_enum = collections.OrderedDict()
        result._value_to_enum = collections.OrderedDict()

        for e in enum:
            if e in result._value_to_enum:
                raise TypeError("Duplicate enum value %d for keys: '%s' and '%s'" % (e, result._value_to_enum[e].key), e.key)

            if e.key in result._key_to_enum:
                raise TypeError("Duplicate enum key '%s' with values: %d and %d" % (e.key, result._key_to_enum[e.key]), e)

            result._key_to_enum[e.key] = e
            result._value_to_enum[e] = e

        return result

    def __getitem__(self, key):
        try:
            key = operator.index(key)
        except TypeError:
            return self._key_to_enum[key]
        else:
            return self._value_to_enum[key]

    def _items(self):
        return self._key_to_enum.items()

    def _keys(self):
        return self._key_to_enum.keys()

    def _values(self):
        return self._key_to_enum.values()

    def items(self):
        return self._items()

    def keys(self):
        return self._keys()

    def values(self):
        return self._values()

class Enum(metaclass=EnumMeta):
    def __getitem__(self, key):
        cls = type(self)
        return type(cls).__getitem__(cls, key)

    def items(cls):
        return cls._items()

    def keys(cls):
        return cls._keys()

    def values(cls):
        return cls._values()

Enum.items = classmethod(Enum.items)
Enum.keys = classmethod(Enum.keys)
Enum.values = classmethod(Enum.values)

class Color(Enum):
    RED, WHITE, BLUE
    GREEN = 4
    YELLOW
    ORANGE = 'orange'
    BLACK

    def dump(self):
        print(self.RED, self.WHITE, self.BLUE, self.GREEN, self.YELLOW, self.BLACK, self.ORANGE, self.dump)

print(Color.RED, Color.WHITE, Color.BLUE, Color.GREEN, Color.YELLOW, Color.BLACK, Color.ORANGE, Color.dump)
Color().dump()
print(repr(Color.RED))
print(repr(Color['RED']))
print(repr(Color().RED))
print(repr(Color()['RED']))
print(repr(Color[0]))
print(repr(Color()[0]))
print(*Color.items())
print(*Color().items())
print(*Color.keys())
print(*Color().keys())
print(*Color.values())
print(*Color().values())

Tim Delaney