Behavior of auto in Enum and Flag.

Ethan Furman ethan at stoneleaf.us
Mon Apr 3 12:29:50 EDT 2017


On 04/03/2017 01:53 AM, Oren Ben-Kiki wrote:
> On Mon, Apr 3, 2017 at 11:03 AM, Ethan Furman wrote:
>
>> 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).
>
> This is not to say that there's no possible workaround for this - the code could pretty easily defer invocation of
> _generate_next_macro_ until after the whole class was seen. It would still happen in order (since members are an ordered
> dictionary these days).

Possible, yes.  Easy, no.

> So it is a matter of conflicting values - what would be more "Pythonic": treating auto as executed immediately, or
> avoiding conflicts between auto and explicit values.

Since most things execute immediately, that is the pattern I chose.

>>> 1. The documentation will be more explicit about the way `auto` behaves in the presence of following value
>>
>> I can do that.
>
> Barring changing the way auto works, that would be best ("explicit is better than implicit" and all that ;-)

> As for backward compatibility, the docs are pretty clear about "use auto when you don't care about the value"... and
> Enum is pretty new, so there's not _that_ much code that relies on "implementation specific" details.

Fair point.

> *If* backward compatibility is an issue here, then the docs might as well specify "previous value plus 1, or 1 if this
> is the first value" as the "standard" behavior, and be done.

Actually, (Int)Flag uses the next power of two (not already seen) -- so it isn't as easy as "last value + 1".

> This has the advantage of being deterministic and explicit, so people would be justified in relying on it. It would
> still have to be accompanied by saying "auto() can only consider previous values, not following ones".

Something to that effect sounds good.

>     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
>
>
> This assumes no following enum values have values > 1000 (or some predetermined constant), which doesn't work in my
> particular case, or in the general case. But yes, this might solve the problem for some people.

General code isn't going to "do what I need" 100% of the time for 100% of the people.  That's why I made the 
_generate_next_value_ method user over-ridable

>>> 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.
>
> Understood. Another alternative would be to have something like _generate_next_value_ex_ with the additional argument
> (similar to __reduce_ex__), which isn't ideal either.

It's a lot more work than just making another method.

> Assuming you buy into my "necessity" claim, that is...

It this point I do not.  If you can give us an example Enum and why it's necessary to be built like that I might be 
swayed -- although the odds are good that the change will go into aenum instead (it's already a mess with the 2.7 
compatibility code...).

> Thanks,

You're welcome.

--
~Ethan~



More information about the Python-list mailing list