classification
Title: implement bool conversion for enums to prevent odd edge case
Type: enhancement Stage: resolved
Components: Versions: Python 3.6
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: ethan.furman Nosy List: ethan.furman, gregory.p.smith, novas0x2a, python-dev, r.david.murray, serhiy.storchaka
Priority: normal Keywords: patch

Created on 2015-08-11 00:09 by novas0x2a, last changed 2016-01-16 20:42 by ethan.furman. This issue is now closed.

Files
File name Uploaded Description Edit
issue24840.master.patch novas0x2a, 2015-08-11 00:18 Patch for master/3.6 that also applies cleanly to 3.5 and 3.4 review
Messages (16)
msg248378 - (view) Author: Mike Lundy (novas0x2a) * Date: 2015-08-11 00:09
There's a slightly odd edge case which can be summarized as follows:

====================
from enum import Enum

class Bool(Enum):
    Yep = True
    Nope = False

for value in Bool:
    print('%18r is %r' % (value, bool(value)))
====================

output:
====================
  <Bool.Yep: True> is True
<Bool.Nope: False> is True
====================

This isn't really a big deal, but can be made better with the attached patch. I can't think of any odd consequences this might cause, but others may know better.
msg248379 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-08-11 00:13
No patch is attached.
msg248380 - (view) Author: Mike Lundy (novas0x2a) * Date: 2015-08-11 00:18
@r.david.murray man you're fast :) Sorry, realized I forgot to actually run the tests for 3.5 and 3.4, I'd only run them for master (I've now run them for 3.5 and 3.4 now, too).
msg248381 - (view) Author: Mike Lundy (novas0x2a) * Date: 2015-08-11 00:20
(I should note that I just recently signed the contributor agreement, not that this a particularly complex fix, but it hasn't propagated to my profile yet).
msg248391 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2015-08-11 05:41
This looks to me as very special case. Enum doesn't delegates the __bool__ method as well as it doesn't delegates __float__, __lt__, __len__, etc. It is left to user to add these methods if needed.

It is easy to add the __bool__ method to user enum. In your case you also can inherit from IntEnum.

>>> class Bool(IntEnum):
...     Yep = True
...     Nope = False
... 
>>> bool(Bool.Yep)
True
>>> bool(Bool.Nope)
False
msg248394 - (view) Author: Mike Lundy (novas0x2a) * Date: 2015-08-11 06:15
@serhiy.storchaka: It's somewhat of a special case, to be sure. However, I do think it's justified to put it into the base (rather than a user type) for three reasons:

1) It makes IntEnum and Enum consistent. IntEnum actually already handles this case just fine, because it's an int and therefore already supports __bool__ correctly. It feels odd that changing the storage format from an IntEnum to a Enum should break the logic- correctly used, the actual enum values should never matter. This small change just brings them into line.

2) It is less surprising than the current case; I discovered this when I did something like the Enum.Nope case here, and I naively used the enum in an if statement, assuming that the value would control the __bool__ value. (This was caught by my tests, of course, but the point remains that I wrote the code). Normally in python, you'd expect the default bool conversion to be unconditionally True, but enums aren't really normal objects; for any use case for which there is a default noop value, you'd generally put that value _into_ the enum:

class FilterType(Enum):
    NONE = None
    SUB  = 'Sub'
    UP   = 'Up'
    ...

3) It's not logically inconsistent with the idea of Enums. The other dunder methods you mention aren't consistent with the concept: __float__ (enum values aren't generally numbers except as an implementation detail), __lt__ (enums aren't generally ordered), __len__ (enums aren't generally containers). The one thing an enum does have is a value, and it feels consistent to me to check the truthiness of an enum without having to reach into the .value to do so.

Anyway, that's my case for inclusion!
msg248408 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2015-08-11 14:59
I agree that it seems odd that testing a 'value' that is false for its truth value would return True.  It is surprising, even if it is an edge case.
msg248442 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2015-08-12 01:51
Thanks for finding that, Mike.

I'll review and merge in the next few days.
msg250932 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2015-09-18 04:49
New changeset 87a9dff5106c by Ethan Furman in branch 'default':
Close issue24840: Enum._value_ is queried for bool(); original patch by Mike Lundy
https://hg.python.org/cpython/rev/87a9dff5106c
msg256869 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2015-12-22 21:23
This change breaks existing code that is relying on the behavior of the enum API as shipped in 3.4 and 3.5.  Please do NOT do this.

Worse, you've landed this change in a "stable" release of the enum34 "backport" module (1.1.1) despite it never having been released as a stable Python API.  Please do not let *the backport* get ahead of the latest stable released Python API.

https://bitbucket.org/stoneleaf/enum34/issues/10/versions-of-enum34-111-break-support-for
msg256872 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2015-12-22 22:11
The enum34 backport has been fixed to not have 3.6 only features (assuming the __bool__ change was the only one).

Was that your only objection, or do you not want this change in 3.6 either?
msg256875 - (view) Author: Gregory P. Smith (gregory.p.smith) * (Python committer) Date: 2015-12-22 22:22
lets collect some examples of where it causes problems (someone ran into the problem at work which is why i hunted down this bugs.python.org issue) before deciding.

If it stays in, it needs a mention in both Misc/NEWS and What's New as it will require some people to test and update their code for 3.6.  Examples will let us decide which way to go.
msg258328 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2016-01-15 21:17
Mike, my apologies.  In the future I'll make sure and read the docs before I go through with changes.

In the docs (https://www.python.org/dev/peps/pep-0435/#id35):

    The reason for defaulting to 1 as the starting number and
    not 0 is that 0 is False in a boolean sense, but enum members
    all evaluate to True.

From a newer discussion on Python Ideas and Python Dev:

Barry Warsaw:
    I think in general enums are primarily a symbolic value and don't
    have truthiness.  It's also so easy to override when you define
    the enum that it's not worth changing the current behavior.

Guido van Rossum:
    Honestly I think it's too late to change. The proposal to change
    plain Enums to False when their value is zero (or falsey) would
    be a huge backward incompatibility. I don't think there's a reasonable
    path forward, and also don't think there's a big reason to regret
    the current semantics.

Thank you, Gregory, for catching that.
msg258339 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2016-01-15 23:05
New changeset: 52dc28ee3c88
msg258340 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2016-01-15 23:18
I think it is better to use "value" instead of "_value_" in the documentation.
msg258407 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2016-01-16 20:42
New changeset e4c22eadc25c:

use public 'value'
History
Date User Action Args
2016-01-16 20:42:03ethan.furmansetmessages: + msg258407
2016-01-15 23:18:00serhiy.storchakasetmessages: + msg258340
2016-01-15 23:05:47ethan.furmansetstatus: open -> closed
resolution: rejected
messages: + msg258339
2016-01-15 21:17:27ethan.furmansetmessages: + msg258328
2015-12-22 22:22:11gregory.p.smithsetmessages: + msg256875
2015-12-22 22:11:48ethan.furmansetmessages: + msg256872
2015-12-22 21:23:58gregory.p.smithsetstatus: closed -> open
versions: + Python 3.6
nosy: + gregory.p.smith

messages: + msg256869

resolution: fixed -> (no value)
2015-09-18 04:49:36python-devsetstatus: open -> closed

nosy: + python-dev
messages: + msg250932

resolution: fixed
stage: patch review -> resolved
2015-08-12 01:51:32ethan.furmansetassignee: ethan.furman
messages: + msg248442
stage: patch review
2015-08-11 14:59:13r.david.murraysetmessages: + msg248408
2015-08-11 06:15:25novas0x2asetmessages: + msg248394
2015-08-11 05:41:14serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg248391
2015-08-11 00:20:36novas0x2asetmessages: + msg248381
2015-08-11 00:18:49novas0x2asetfiles: + issue24840.master.patch
keywords: + patch
messages: + msg248380
2015-08-11 00:16:59yselivanovsetnosy: + ethan.furman
2015-08-11 00:13:49r.david.murraysetnosy: + r.david.murray
messages: + msg248379
2015-08-11 00:09:32novas0x2acreate