Repository navigation
Iterating over enum.Flag types ignores zero-valued in Python 3.11 #109633
Description
Activity
- addedtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
on Sep 21, 2023 I bisected to here:
commit 7aaeb2a
Author: Ethan Furman ethan@stoneleaf.us
Date: Mon Jan 25 14:26:19 2021 -0800
bpo-38250: [Enum] single-bit flags are canonical (GH-24215)From the docs, I can say that now
0is treated as more like a special value:
https://docs.python.org/3/library/enum.html#enum.FlagCheck how
<Color: 0>is used all over the place.The technical reason behind this is that now
0does not get into_member_names_here:Lines 313 to 319 in 712cb17
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
0is not a_is_single_bitmember (it is explicit):Lines 93 to 98 in 712cb17
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:Lines 790 to 794 in 712cb17
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-GlennIt is more accurate to say that
EnumandFlagiterate 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, BLUEAliases 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.
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.
>>> class F(enum.Flag): ... f = 0 ... >>> len(F) 0 >>> list(F) []Why isn't
F.fconsidered to be canonical?Because
fis not a flag, it is a name for the absence of flags.fshows up inF.__members__, but not indir(F). Is that intended?Yes.
dir(F)doesn't list aliases, but__members__does (which also prevents aliases from being overridden -- i.e.F.f = 99raises anAttributeError).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?
@ClundXIII You can do this
from enum import IntFlag class Bla(IntFlag): X = 0 list(Bla.__members__.values())
Bug report
Bug description:
In Python 3.10, you can iterate over
enum.Enumandenum.Flagtypes to yield their values.In Python 3.11, the same is true for
enum.Enumtypes, but the behavior seems to have changed forenum.Flagtypes. Now, iterating over the type only yields its nonzero values.CPython versions tested on:
3.10, 3.11
Operating systems tested on:
Linux