classification
Title: Alternate approach to aliasing for PEP 435
Type: enhancement Stage: resolved
Components: Versions: Python 3.4
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: alex, barry, dilettant, eric.smith, ethan.furman, gvanrossum, ncoghlan, pitrou
Priority: high Keywords:

Created on 2013-05-12 06:51 by ncoghlan, last changed 2013-05-14 03:56 by ncoghlan. This issue is now closed.

Messages (12)
msg188981 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 06:51
Creating this as a separate issue so as not to delay incorporation of the accepted PEP.

One legitimate criticism of the accepted PEP 435 is that the combination of requiring explicit assignment of values *and* allowing aliasing by default is that aliases may be created inadvertently. I believe we can actually do better than the initial implementation by making the following work:

>>> class Shape(Enum):
...   square = 2
...   diamond = 1
...   circle = 3
...   alias_for_square = square
...
>>> Shape.square
<Shape.square: 2>
>>> Shape.alias_for_square
<Shape.square: 2>
>>> Shape(2)
<Shape.square: 2>

While *disallowing* the following: 

>>> class Shape(Enum):
...   square = 2
...   diamond = 1
...   circle = 3
...   alias_for_square = 2
...

How, do you ask? By wrapping non-descriptors on assignment in a placeholder type, and keeping track of which values we have already seen. If a new attribute is mapped to a placeholder, then that's fine, we accept it as an explicit declaration of an alias. However, if it's mapped directly to a repeat value, then that would be disallowed (as it was in earlier versions of the PEP).
msg188983 - (view) Author: Alex Gaynor (alex) * (Python committer) Date: 2013-05-12 06:54
This would preclude code like:

class Shape(Enum):
  rectangle = shape = 2

which seems (to me) to be the most reasonable way to express aliasing.
msg188988 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 07:56
That's a terrible way to express aliasing, because it's really unclear that "rectangle" is the canonical name.

By contrast:

  class Shape(Enum):
    rectangle = 2
    oblong = rectangle

leaves no doubt as to which is canonical and which is the alias. You never need to go beyond two assignments, as you can still use multiple target assignment for the aliases.
msg189008 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-05-12 10:44
Another approach to handling this, and other, issues is to allow options to EnumMeta.  My original aenum code had the default Enum class as unordered, no duplicates allowed, non-indexable, etc., but then allowed options to be passed in such as DUPLICATES to allow duplicates, ORDERED to add the ge-gt-le-lt methods, INDEXED to add __index__, etc.

For the aliasing issue this method is more robust as placeholders are not required, and code like this will work:

class Physics(Enum):
    e = 2.81847
    pi = 3.141596
    tau = 2 * pi

To make that code work with placeholders is possible (I have it in aenum) but a major pain (I was about to remove it before my offer to help with ref435 was accepted).
msg189024 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 12:44
Hmm, that's an interesting point about allowing operations on the already defined values.

You could get around that by requiring that the wrapper be explicit when definined the alias:

>>> class Shape(enum.Enum):
...   rectangle = 1
...   oblong = enum.alias(rectangle)

Or, equivalently:


>>> class Shape(enum.Enum):
...   rectangle = 1
...   oblong = enum.alias(1)

So simple typos would trigger an error by default, and the enum.alias wrapper would tell the namespace (or the metaclass) "hey, this should be an alias for another value already defined here".

I definitely don't want us to turn the metaclass into a swiss army knife of behavioural options - I'm happy with customisation hooks in the metaclass on that front, as I believe it is an effective way to discourage excessive use of metaclass magic without preventing it when it is necessary.
msg189042 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-05-12 16:12
That's way too much magic for my taste.
msg189043 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2013-05-12 16:14
Also, since we currently don't forbid the following:

>>> {'a': 1, 'a': 2}
{'a': 2}

I don't see why enums should be any different. Recommend closing.
msg189071 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-12 23:21
The difference is the use case: dicts are a general purpose data store,
enums are typically for defining mutually exclusive named values, with
aliasing as a concession to practical reality.
msg189074 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2013-05-12 23:41
I'm with Antoine. Trying to prevent accidental redefinition behavior like
Nick proposes feels like un-Pythonic coddling to me, and I expect we'd be
closing off some occasionally useful patterns. Please leave well enough
alone.
msg189083 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-13 00:06
OK, I mainly wanted to make sure this alternative was at least considered, as I didn't see it come up during the PEP discussions.
msg189138 - (view) Author: Barry A. Warsaw (barry) * (Python committer) Date: 2013-05-13 14:26
On May 12, 2013, at 06:51 AM, Nick Coghlan wrote:

>>>> class Shape(Enum):
>...   square = 2
>...   diamond = 1
>...   circle = 3
>...   alias_for_square = square

I see Guido pronounced against it, but I'm just registering that I kind of
like this.  You could probably have guess that since flufl.enum doesn't allow
aliases at all mostly because of the potential for accidental duplicate
values, which this would avoid.

Hmm.  LP: #1179529
msg189197 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2013-05-14 03:56
I just wanted to note that there's a trivial way to prevent accidental aliases inline or in your test suite if you don't intend them:

    class MyEnum(Enum):
        ....

    assert len(MyEnum) == len(MyEnum.__members__)
History
Date User Action Args
2013-05-14 03:56:24ncoghlansetmessages: + msg189197
2013-05-13 14:26:48barrysetmessages: + msg189138
2013-05-13 14:21:44barrysetnosy: + barry
2013-05-13 00:06:17ncoghlansetstatus: open -> closed
messages: + msg189083

dependencies: - Code, test, and doc review for PEP-0435 Enum
resolution: rejected
stage: needs patch -> resolved
2013-05-12 23:41:46gvanrossumsetmessages: + msg189074
2013-05-12 23:21:49ncoghlansetmessages: + msg189071
2013-05-12 16:14:01pitrousetmessages: + msg189043
2013-05-12 16:12:42pitrousetnosy: + pitrou
messages: + msg189042
2013-05-12 16:07:01dilettantsetnosy: + dilettant
2013-05-12 15:07:24gvanrossumsetnosy: + gvanrossum
2013-05-12 12:44:41ncoghlansetmessages: + msg189024
2013-05-12 10:44:26ethan.furmansetnosy: + ethan.furman
messages: + msg189008
2013-05-12 10:31:49eric.smithsetnosy: + eric.smith
2013-05-12 10:11:30ncoghlansetpriority: normal -> high
2013-05-12 07:56:05ncoghlansetmessages: + msg188988
2013-05-12 06:54:08alexsetnosy: + alex
messages: + msg188983
2013-05-12 06:51:58ncoghlansetdependencies: + Code, test, and doc review for PEP-0435 Enum
2013-05-12 06:51:37ncoghlancreate