Skip to content

Inverted enum.Flag #107538

Closed
Closed

Description

@GrafLearnt

Bug report

Checklist

  • I am confident this is a bug in CPython, not a bug in a third-party project
  • I have searched the CPython issue tracker, and am confident this bug has not been reported before

A clear and concise description of the bug

Adding inverted option to enum.Flag brakes logic of enum.
I assume it caused by ambiguous implementation of bitwise NOT in Flag which processes ~value option different inside class scope and outside class scope unlike bitwise OR

import enum

class X(enum.Flag):
  a = enum.auto()
  b = enum.auto()
  c = a | b


assert list(X) == [X.a, X.b]
assert ~X.a == X.b
assert list(~X.a) == [X.b]


class Y(enum.Flag):
  a = enum.auto()
  b = enum.auto()
  c = a | b
  d = ~a  # this line brakes the code


assert list(Y) == [Y.a, Y.b]
assert ~Y.a == Y.b  # AssertionError
assert list(~Y.a) == [Y.b]  # ValueError: -2 is not a positive integer

Traceback:

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1482, in __iter__
  yield from self._iter_member_(self._value_)
File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1359, in _iter_member_by_value_
  for val in _iter_bits_lsb(value & cls._flag_mask_):
File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 122, in _iter_bits_lsb
  raise ValueError('%r is not a positive integer' % original)
ValueError: -2 is not a positive integer

Your environment

  • CPython versions tested on: Clang 14.0.3 (clang-1403.0.22.14.1)
  • Operating system and architecture: 22.5.0 Darwin Kernel Version 22.5.0: Thu Jun 8 22:22:20 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6000 arm64

Linked PRs

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Aug 1, 2023
  2. Eclips4 commented on Aug 1, 2023

    @Eclips4
    Member

    Cannot reproduce on current main. Seems that this case fixed by #105542

  3. ethanfurman commented on Aug 1, 2023

    @ethanfurman
    Member

    It's not failing, and the first three work correctly, but Y.d should be an alias for Y.b, not -2.

  4. GrafLearnt commented on Aug 1, 2023

    @GrafLearnt
    Author

    @ethanfurman may be you are right but I find following quite confusing:
    ~a changed while a remaining the same

    class X(enum.Flag):
        a = enum.auto()
        b = enum.auto()
        c = enum.auto()
    >>> list(X)
    [<X.a: 1>, <X.b: 2>, <X.c: 4>]
    >>> ~X.a
    <X.b|c: 6>
    >>> list(~X.a)
    [<X.b: 2>, <X.c: 4>]
    class Y(enum.Flag):
        a = enum.auto()
        b = enum.auto()
        c = enum.auto()
        d = ~a
    >>> list(Y)
    [<Y.a: 1>, <Y.b: 2>, <Y.c: 4>]
    >>> ~Y.a
    <Y.d: -2>
    >>> list(~Y.a)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1482, in __iter__
        yield from self._iter_member_(self._value_)
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1359, in _iter_member_by_value_
        for val in _iter_bits_lsb(value & cls._flag_mask_):
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 122, in _iter_bits_lsb
        raise ValueError('%r is not a positive integer' % original)
    ValueError: -2 is not a positive integer
  5. ethanfurman commented on Aug 1, 2023

    @ethanfurman
    Member

    @GrafLearnt, in your latest example, d should be 6 (i.e. b|c). Thanks for the reminder, this will be a more involved fix than I was originally thinking.

  6. GrafLearnt commented on Aug 1, 2023

    @GrafLearnt
    Author

    @ethanfurman d = b | c will work perfectly fine until someone add new member without adding it to d

  7. ethanfurman commented on Aug 1, 2023

    @ethanfurman
    Member

    Well, presumably, if they write d = b|c that's what they want; while d = ~a means every member that's not a.

  8. GrafLearnt commented on Aug 1, 2023

    @GrafLearnt
    Author

    @ethanfurman
    I love it:

    class X(enum.Flag):
        a = enum.auto()
        b = enum.auto()
        c = enum.auto()
    >>> ~X.a
    <X.b|c: 6>

    But this doesn't look correct to me:

    class Y(enum.Flag):
        a = enum.auto()
        b = enum.auto()
        c = enum.auto()
        d = ~a
    >>> ~Y.a
    <Y.d: -2>

    Kindly ask to clarify if I am allowed to define inverted.
    It brakes iteration of ~Y.a
    Also I get following:

    >>> ~Y.b
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1542, in __invert__
        self._inverted_ = self.__class__(self._flag_mask_ ^ self._value_)
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 711, in __call__
        return cls.__new__(cls, value)
               ^^^^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1136, in __new__
        raise exc
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1113, in __new__
        result = cls._missing_(value)
                 ^^^^^^^^^^^^^^^^^^^^
      File "/opt/homebrew/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/enum.py", line 1399, in _missing_
        raise ValueError(
    ValueError: <flag 'Y'> invalid value -3
        given 0b1 01
      allowed 0b1 11
  9. ethanfurman commented on Aug 1, 2023

    @ethanfurman
    Member

    Defining ~a as a member is what is not working correctly.

    I believe that last error is fixed in 3.11.5.

  10. GrafLearnt commented on Aug 1, 2023

    @GrafLearnt
    Author

    @ethanfurman is defining inverted restricted or will be fixed?

    Works fine in Python 3.10.12:

    >>> ~Y.b
    <Y.c|a: 5>
  11. ethanfurman commented on Aug 1, 2023

    @ethanfurman
    Member

    Will be fixed.

  12. added a commit that references this issue on Jul 10, 2025
  13. added a commit that references this issue on Jul 11, 2025
  14. added a commit that references this issue on Jul 12, 2025
  15. added a commit that references this issue on Jul 13, 2025
  16. added a commit that references this issue on Aug 4, 2025
  17. added a commit that references this issue on Aug 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions