PEP 435 - reference implementation discussion

Split off from the PEP 435 - requesting pronouncement thread. Think I've come up with a system that works for my auto-numbering case without knowing the internals of enum_type. Patch passes all existing test cases. The patch does two things: 1. Finds the first non-Enum class on the MRO of the new class and uses that as the enum type. 2. Instead of directly setting the _name and _value of the enum_item, it lets the Enum class do it via Enum.__init__(). Subclasses can override this. This gives Enums a 2-phase construction just like other classes. diff -r 758d43b9f732 ref435.py --- a/ref435.py Fri May 03 18:59:32 2013 -0700 +++ b/ref435.py Sun May 05 13:43:56 2013 +1000 @@ -116,7 +116,17 @@ if bases[-1] is Enum: obj_type = bases[0] else: - obj_type = bases[-1].__mro__[1] # e.g. (IntEnum, int, Enum, object) + obj_type = None + + for base in bases: + for c in base.__mro__: + if not issubclass(c, Enum): + obj_type = c + break + + if obj_type is not None: + break + else: obj_type = object # save enum items into separate mapping so they don't get baked into @@ -143,8 +153,7 @@ enum_item = object.__new__(enum_class) else: enum_item = obj_type.__new__(enum_class, value) - enum_item._value = value - enum_item._name = e + enum_item.__init__(e, value) enum_map[e] = enum_item enum_class.__aliases__ = aliases # non-unique enums names enum_class._enum_names = enum_names # enum names in definition order @@ -232,6 +241,10 @@ return enum raise ValueError("%s is not a valid %s" % (value, cls.__name__)) + def __init__(self, name, value): + self._name = name + self._value = value + def __repr__(self): return "<%s.%s: %r>" % (self.__class__.__name__, self._name, self._value) Auto-int implementation: class AutoInt(int): __slots__ = () def __new__(cls, value): if value is Ellipsis: try: i = cls._auto_number except AttributeError: i = cls._auto_number = 0 else: i = cls._auto_number = value cls._auto_number += 1 return int.__new__(cls, i) class AutoIntEnum(AutoInt, IntEnum): def __init__(self, name, value): super(AutoIntEnum, self).__init__(name, int(self)) class TestAutoIntEnum(AutoIntEnum): a = ... b = 3 c = ... class TestAutoIntEnum2(AutoIntEnum): a = ... b = ... c = ... print(TestAutoIntEnum, list(TestAutoIntEnum)) print(TestAutoIntEnum2, list(TestAutoIntEnum2)) ---------- Run ---------- <Enum 'TestAutoIntEnum'> [<TestAutoIntEnum.a: 0>, <TestAutoIntEnum.b: 3>, <TestAutoIntEnum.c: 4>] <Enum 'TestAutoIntEnum2'> [<TestAutoIntEnum2.a: 0>, <TestAutoIntEnum2.b: 1>, <TestAutoIntEnum2.c: 2>] Tim Delaney

On 05/04/2013 08:50 PM, Tim Delaney wrote:
This is good. :)
2. Instead of directly setting the _name and _value of the enum_item, it lets the Enum class do it via Enum.__init__(). Subclasses can override this. This gives Enums a 2-phase construction just like other classes.
Not sure I care for this. Enums are, at least in theory, immutable objects, and immutable objects don't call __init__. Of course, practicality beats purity... I'll have to think about this some more. Fortunately, none of this has any bearing on the PEP itself. -- ~Ethan~

On 05/04/2013 10:59 PM, Ethan Furman wrote:
[snip]
Okay, still thinking about `value`, but as far as `name` goes, it should not be passed -- it must be the same as it was in the class definition or we could end up with something like: --> class AreYouKiddingMe(WierdEnum): ... who = 1 ... what = 2 ... when = 3 ... where = 4 ... why = 5 --> list(AreYouKiddingMe) [ <AreYouKiddingMe.him: 1>, <AreYouKiddingMe.that: 2>, <AreYouKiddingMe.now: 3>, <AreYouKiddingMe.here: 4>, <AreYouKiddingMe.because: 5>, ] and that's assuming we made more changes to support such insane behavior; otherwise it would just break. So no passing of `name`, it gets set in the metaclass. -- ~Ethan~

On 5 May 2013 21:58, Tim Delaney <timothy.c.delaney@gmail.com> wrote:
*If* I can manage to convince Guido and Eli over in that other (initial values) thread, I think it's still probably worthwhile calling __init__ on the enum instance, but with no parameters. That would allow more behaviour-based enums to set up any other initial state they require. Tim Delaney

On 05/04/2013 08:50 PM, Tim Delaney wrote:
This is good. :)
2. Instead of directly setting the _name and _value of the enum_item, it lets the Enum class do it via Enum.__init__(). Subclasses can override this. This gives Enums a 2-phase construction just like other classes.
Not sure I care for this. Enums are, at least in theory, immutable objects, and immutable objects don't call __init__. Of course, practicality beats purity... I'll have to think about this some more. Fortunately, none of this has any bearing on the PEP itself. -- ~Ethan~

On 05/04/2013 10:59 PM, Ethan Furman wrote:
[snip]
Okay, still thinking about `value`, but as far as `name` goes, it should not be passed -- it must be the same as it was in the class definition or we could end up with something like: --> class AreYouKiddingMe(WierdEnum): ... who = 1 ... what = 2 ... when = 3 ... where = 4 ... why = 5 --> list(AreYouKiddingMe) [ <AreYouKiddingMe.him: 1>, <AreYouKiddingMe.that: 2>, <AreYouKiddingMe.now: 3>, <AreYouKiddingMe.here: 4>, <AreYouKiddingMe.because: 5>, ] and that's assuming we made more changes to support such insane behavior; otherwise it would just break. So no passing of `name`, it gets set in the metaclass. -- ~Ethan~

On 5 May 2013 21:58, Tim Delaney <timothy.c.delaney@gmail.com> wrote:
*If* I can manage to convince Guido and Eli over in that other (initial values) thread, I think it's still probably worthwhile calling __init__ on the enum instance, but with no parameters. That would allow more behaviour-based enums to set up any other initial state they require. Tim Delaney
participants (2)
-
Ethan Furman
-
Tim Delaney