FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Iterating over `enum.Flag` types ignores zero-valued in Python 3.11 · Issue #109633 · python/cpython · GitHub

Repository navigation

Iterating over enum.Flag types ignores zero-valued in Python 3.11 #109633

Closed

Description

Bug 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

Activity

  1. CharlieZhao95 commented on Sep 21, 2023

    Contributor

    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)

  2. sobolevn commented on Sep 21, 2023

    Member

    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:

    cpython/Lib/enum.py

    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 0 is not a _is_single_bit member (it is explicit):

    cpython/Lib/enum.py

    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:

    cpython/Lib/enum.py

    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 :)

  3. self-assigned this
    on Sep 21, 2023
  4. ethanfurman commented on Sep 21, 2023

    Member

    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.

  5. gmgunter commented on Sep 22, 2023

    Author

    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.

  6. wimglenn commented on Jun 27, 2024

    Contributor
    >>> class F(enum.Flag):
    ...     f = 0
    ... 
    >>> len(F)
    0
    >>> list(F)
    []
    

    Why isn't F.f considered to be canonical?

  7. ethanfurman commented on Jun 27, 2024

    Member

    Because f is not a flag, it is a name for the absence of flags.

  8. wimglenn commented on Jun 27, 2024

    Contributor

    f shows up in F.__members__, but not in dir(F). Is that intended?

  9. ethanfurman commented on Jun 28, 2024

    Member

    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).

  10. 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?

  11. Leo1690 commented on Mar 21, 2025

    @ClundXIII You can do this

    from enum import IntFlag
    
    class Bla(IntFlag):
        X = 0
    
    list(Bla.__members__.values())
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

type-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


    Back | FazBrowse Home | New Git URL