Behavior of auto in Enum and Flag.

Ethan Furman ethan at stoneleaf.us
Mon Apr 3 04:03:05 EDT 2017


On 04/02/2017 09:49 PM, Oren Ben-Kiki wrote:

> The current behavior of `auto` is to pick a value which is one plus the
> previous value.

Starting with 1 if no previous value exists.

> It would probably be better if `auto` instead picked a value that is not
> used by any named member (either the minimal unused value, or the minimal
> higher than the previous value). That is, in this simple case:
>
>      class MyEnum(Enum):
>          FOO = 1
>          BAR = auto()
>          BAZ = 2
>
> It would be far better for BAR to get the value 3 rather than today's value
> 2.

Possibly.

> After all, `auto` is supposed to be used when:
>
> "If the exact value is unimportant you may use auto instances and an
> appropriate value will be chosen for you."
>
> Choosing a value that conflicts with BAZ in above cases doesn't seem
> "appropriate" for a value that is "unimportant".

MyEnum.BAR exists, and works.  It just happens to be the same as MyEnum.BAZ -- which did not exist when the value for 
BAR was chosen.

> The docs also state "Care must be taken if you mix auto with other values."
> - fair enough. But:
>
> First, why require "care" if the code can take care of the issue for us?
>
> Second, the docs don't go into further detail about what exactly to avoid.
> In particular, the docs do not state that the automatic value will only
> take into account the previous values, and will ignore following values.

Python code is executed top-down.  First FOO, then BAR, then BAZ.  It is not saved up and executed later in random 
order.  Or, put another way, the value was appropriate when it was chosen -- it is not the fault of auto() that the user 
chose a conflicting value (hence why care should be taken).

> However, this restriction is baked into the current implementation:
> It is not possible to just override `_generate_next_value_` to skip past
> named values which were not seen yet, because the implementation only
> passes it the list of previous values.

In other words, the currently existing values, because the future values don't exist yet.

> I propose that:
>
> 1. The documentation will be more explicit about the way `auto` behaves in
> the presence of following values.

I can do that.

> 2. The default behavior of `auto` would avoid generating a conflict with
> following values.

I could do that, but I'm not convinced it's necessary, plus there would be backwards compatibility constraints at this 
point.

> 3. Whether `auto` chooses (A) the minimal unused value higher than the
> previous value, or (B) the minimal overall unused value, or (C) some other
> strategy, would depend on the specific implementation.

This might work for you (untested):

def _generate_next_value_(name, start, count, previous_values):
     if not count:
         return start or 1
     previous_values.sort()
     last_value = previous_values[-1]
     if last_value < 1000:
         return 1001
     else:
         return last_value + 1

> 3. To allow for this, the implementation will include a
> `_generate_auto_value_` which will take both the list of previous ("last")
> values (including auto values) and also a second list of the following
> ("next") values (excluding auto values).

No, I'm not interested in doing that.  I currently have that kind of code in aenum[1] for 2.7 compatibility, and it's a 
nightmare to maintain.

--
~Ethan~


[1] https://pypi.python.org/pypi/aenum
     Advanced Enumerations
     NamedConstants
     NamedTuple (metaclass based namedtuple -- no code eval)
     plus a bunch of other goodies


More information about the Python-list mailing list