GeezerVison is showing github.com — Iterating over `enum.Flag` types ignores zero-valued in Python 3.11 · Issue #109633 · python/cpython
Iterating over enum.Flag types ignores zero-valued in Python 3.11 #109633DescriptionBug report
Bug description:
In Python 3.10, you can iterate over enum.Enum and enum.Flag types to yield their values.
>>> import sys; print(sys.version)
3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:40:32) [GCC 12.3.0]
>>> import enum
>>> class MyEnum(enum.Enum):
... A = 0
... B = 1
... C = 2
...
>>> list(MyEnum)
[<MyEnum.A: 0>, <MyEnum.B: 1>, <MyEnum.C: 2>]
>>> class MyFlag(enum.Flag):
... A = 0
... B = 1
... C = 2
...
>>> list(MyFlag)
[<MyFlag.A: 0>, <MyFlag.B: 1>, <MyFlag.C: 2>]
In Python 3.11, the same is true for enum.Enum types, but the behavior seems to have changed for enum.Flag types. Now, iterating over the type only yields its nonzero values.
>>> import sys; print(sys.version)
3.11.5 | packaged by conda-forge | (main, Aug 27 2023, 03:34:09) [GCC 12.3.0]
>>> import enum
>>> class MyEnum(enum.Enum):
... A = 0
... B = 1
... C = 2
...
>>> list(MyEnum)
[<MyEnum.A: 0>, <MyEnum.B: 1>, <MyEnum.C: 2>]
>>> class MyFlag(enum.Flag):
... A = 0
... B = 1
... C = 2
...
>>> list(MyFlag)
[<MyFlag.B: 1>, <MyFlag.C: 2>] # Missing <MyFlag.A: 0> (!)
CPython versions tested on:
3.10, 3.11
Operating systems tested on:
Linux ActivityCharlieZhao95 commented on Sep 21, 2023 I bisected to here:
commit 7aaeb2a
Author: Ethan Furman [email protected]
Date: Mon Jan 25 14:26:19 2021 -0800
bpo-38250: [Enum] single-bit flags are canonical (GH-24215)
sobolevn commented on Sep 21, 2023 From the docs, I can say that now 0 is treated as more like a special value:
https://docs.python.org/3/library/enum.html#enum.Flag
Check how <Color: 0> is used all over the place.
The technical reason behind this is that now 0 does not get into _member_names_ here:
|
elif ( |
|
Flag is not None |
|
and issubclass(enum_class, Flag) |
|
and _is_single_bit(value) |
|
): |
|
# no other instances found, record this member in _member_names_ |
|
enum_class._member_names_.append(member_name) |
Because 0 is not a _is_single_bit member (it is explicit):
|
def _is_single_bit(num): |
|
""" |
|
True if only one bit set in num (should be an int) |
|
""" |
|
if num == 0: |
|
return False |
So, when we use __iter__ over _member_names_ zero-value is not there:
|
def __iter__(cls): |
|
""" |
|
Return members in definition order. |
|
""" |
|
return (cls._member_map_[name] for name in cls._member_names_) |
We need @ethanfurman :) Reacted by Charlie Zhao and Wim Jeantine-Glenn ethanfurman commented on Sep 21, 2023 It is more accurate to say that Enum and Flag iterate over the canonical values. For example:
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
ROJO = 1
VERDE = 2
AZUL = 3
list(Color)
# RED, GREEN, BLUE
class BlendColor(Flag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
WHITE = RED | GREEN | BLUE
list(BlendColor)
# RED, GREEN, BLUE
Aliases have always been omitted from iteration, and flag values with no, or more than one, bit(s) set are considered aliases since Python 3.11 -- this was considered a bug-fix. gmgunter commented on Sep 22, 2023 Oh I see. Thanks for the clarification, @ethanfurman.
Looking back over the docs now, they do pretty much convey everything that you've explained here, although some of the details are subtle. The only thing that I think isn't explicitly explained there is that zero-valued flags are considered aliases. wimglenn commented on Jun 27, 2024 >>> class F(enum.Flag):
... f = 0
...
>>> len(F)
0
>>> list(F)
[]
Why isn't F.f considered to be canonical? ethanfurman commented on Jun 27, 2024 Because f is not a flag, it is a name for the absence of flags. wimglenn commented on Jun 27, 2024 f shows up in F.__members__, but not in dir(F). Is that intended?
ethanfurman commented on Jun 28, 2024 Yes. dir(F) doesn't list aliases, but __members__ does (which also prevents aliases from being overridden -- i.e. F.f = 99 raises an AttributeError). ClundXIII commented on Dec 5, 2024 Hi,
I do have a use case where I need both alias and elements of a flag. Is there a way to explicitly iterate over either only elements and only aliases that works in both old AND new version? Leo1690 commented on Mar 21, 2025 @ClundXIII You can do this
from enum import IntFlag
class Bla(IntFlag):
X = 0
list(Bla.__members__.values())
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|