classification
Title: Support creation of extensible enums through metaclass subclassing
Type: enhancement Stage: needs patch
Components: Versions: Python 3.4
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: barry, ethan.furman, ncoghlan
Priority: low Keywords:

Created on 2013-05-11 13:55 by ncoghlan, last changed 2013-05-14 03:56 by barry. This issue is now closed.

Messages (8)
msg188920 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-11 13:55
Guido chose to follow Java in enforcing the invariant "Enum members are instances of that Enum" for PEP 435 (i.e. "assert (all(isinstance(x, SomeEnum) for x in SomeEnum)"). As a consequence, the Enum metaclass prevents subclassing of Enums with defined members.

This is a reasonable design choice, but one that limits the applicability of the standard library enum solution for use cases that currently rely on this feature of a custom enum implementation (including flufl.enum, the original inspiration for this feature).

An alternative reasonable design choice is to allow extension of enumerations (similar to flufl.enum) and relax the invariant to "Enum members are an instance of that Enum or an Enum-derived parent class of that Enum" (i.e. "assert (all(issubclass(type(x), Enum) and type(x) in SomeEnum.mro() for x in SomeEnum)")

There is no need to support this directly in the standard library, but it would be valuable to make it straightforward to support in an Enum variant by subclassing the standard metaclass (similar to the customisation mechanisms provided to support things like autonumbered enums through a derived metaclass). Currently, implementing this behaviour involves overriding a private method of the metaclass (EnumMetaclass._get_mixins)
msg188978 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-05-12 05:42
I'm sure this is a dumb question, but I have lots of them so thought I'd share.

Can this issue be resolved simply by making the `_get_mixins` method `get_mixins`?
msg188980 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 06:30
I don't think that would be wise - there's currently other logic in _get_mixins that subclasses would then have to duplicate.

I was thinking more of an allow_subclass() API that subtypes could override to always return True. EnumMeta would then just factor the relevant piece of _get_mixins out into the default allow_subclass implementation.
msg188998 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 10:31
I elaborated on this point in http://python-notes.boredomandlaziness.org/en/latest/python3/enum_creation.html#support-for-alternate-declaration-syntaxes

However, I'm now wondering if the problem is simply that the "no extension of enums" rule is more restrictive than it needs to be. If you *don't define any new methods*, then there's no problem with extending an enumeration - it's only the combination of extension and adding extra behaviour which is incoherent.
msg189015 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-05-12 11:02
Make it simpler:

class EnumMeta():
    allow_subclass = False


class MyEnumMeta(EnumMeta):
    allow_subclass = True
msg189025 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 12:52
Simply removing the restriction isn't actually appropriate, as the variants that allow addition of new members should *not* allow addition of new descriptors.

That's why I'm wondering if the current subclassing restriction is wrong: if you subclass an Enum derivative that already has defined members, then adding new members is OK, but adding new behaviour is not. If you subclass an Enum derivative with no members, then adding either members or behaviours is fine.

If we relaxed the "no subclassing" rule to "no new non-members", then this Enums would be natively extensible and this customisation hack wouldn't be necessary at all.

We have plenty of time before 3.4 - let's get the existing implementation in before worrying further about this.
msg189030 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-05-12 13:17
Not trying to push, but if I don't write it down now, I'll forget later. ;)

What does adding new members gain us?  If I have a func xyz() that's expecting a Color, a MoreColor will work sometimes and blow up other times.

Or are you saying that we may have a function mno() that takes a Color or a MoreColor?
msg189032 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 13:32
Ah, you're right, I forgot that was the other reason for disallowing extensions through subclassing.

To get extensions to work right, you need to flip it around so that isinstance(Color.red, MoreColor) is True, while isinstance(MoreColor.magenta, Color) is False.
History
Date User Action Args
2013-05-14 03:56:30barrysetnosy: + barry
2013-05-12 13:32:06ncoghlansetstatus: open -> closed
resolution: not a bug
dependencies: - Code, test, and doc review for PEP-0435 Enum
messages: + msg189032
2013-05-12 13:17:31ethan.furmansetmessages: + msg189030
2013-05-12 12:52:34ncoghlansetmessages: + msg189025
2013-05-12 11:02:27ethan.furmansetmessages: + msg189015
2013-05-12 10:31:23ncoghlansetmessages: + msg188998
2013-05-12 06:30:06ncoghlansetmessages: + msg188980
2013-05-12 05:42:15ethan.furmansetnosy: + ethan.furman
messages: + msg188978
2013-05-11 13:55:45ncoghlansetdependencies: + Code, test, and doc review for PEP-0435 Enum
2013-05-11 13:55:34ncoghlancreate