Skip to content

enum.Flag.__contains__ changed behavior since python 3.12 #131045

Description

@timrid

Bug report

Bug description:

I noticed some strange behavior with enum.Flag an the __contains__ method in Python 3.12/3.13, as shown in the following examples.

Problem 1: Behavior changes at runtime
In the following code snippet the first print statement returns False, which is expected, since 3 is not a member of Weekday. However, the second print statement returns True, which is unexpected, since 3 is still not a member of Weekday.

import enum

class Weekday(enum.Flag):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 4
    THURSDAY = 8
    FRIDAY = 16
    SATURDAY = 32
    SUNDAY = 64

print(f"{3 in Weekday}") # => False (expected)
_ = Weekday.MONDAY | Weekday.TUESDAY
print(f"{3 in Weekday}") # => True (not expected)

Problem 2: Behavior is not comparable to Python 3.11
Since Python 3.12 the behavior of Enum.__contains__ has changed, so that it is possible to compare not only with an enum-member, but also with non-enum-members (see here or here). So with Python 3.11 the code above will raise an TypeError: unsupported operand type(s) for 'in': 'int' and 'EnumType'. There you have to change the code to the following. But this in turn always produces unexpected behavior in Python 3.12/3.13.

import enum

class Weekday(enum.Flag):
    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 4
    THURSDAY = 8
    FRIDAY = 16
    SATURDAY = 32
    SUNDAY = 64

print(f"{Weekday(3) in Weekday}") # => Python 3.11: False (expected)    Python 3.12/3.13: True (not expected)
_ = Weekday.MONDAY | Weekday.TUESDAY
print(f"{Weekday(3) in Weekday}") # => Python 3.11: False (expected)    Python 3.12/3.13: True (not expected)

Conclusion
I would have expected that in all cases the result is False, but since Python 3.12 it gets very strange...

CPython versions tested on:

3.12, 3.13

Operating systems tested on:

Windows

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    on Mar 10, 2025
  2. added
    stdlibStandard Library Python modules in the Lib/ directory
    3.12only security fixes
    3.13only security fixes
    3.14bugs and security fixes
    on Mar 10, 2025
  3. picnixz commented on Mar 10, 2025

    @picnixz
    Member

    1 | 2 becomes 3 and I think your surprise comes from the fact that a new member is automatically created. I think you can avoid this starting from 3.12 by specifying the boundary (namely a new member is not automatically created). I haven't checked but this should also have been documented somehow maybe indirectly.

    I believe that the reason is because the result of the OR of two enum flag members should remain an enum flag member by default (3.12+) and thus we create that additional member on the fly.

    With a different boundary the result would be a pure int and not an enum member. To check if this diagnosis is correct, print the type of the _ in your code.

    I can check on Wednesday as I'm unavailable until then (and I'm only answering from memory)

  4. added
    pendingThe issue will be closed if no feedback is provided
    on Mar 10, 2025
  5. tomasr8 commented on Mar 10, 2025

    @tomasr8
    Member

    This behaviour is documented for enum.IntFlag:

    If a Flag operation is performed with an IntFlag member and:

    • the result is a valid IntFlag: an IntFlag is returned
    • the result is not a valid IntFlag: the result depends on the FlagBoundary setting

    Perhaps we should mention this in enum.Flag as well?

  6. ethanfurman commented on Mar 10, 2025

    @ethanfurman
    Member

    The intent is that __contains__ will return True if either a member or a member value exists. In other words, if Weekday(x) would return a valid member, then x in Weekday should return True. The issue you found is that pseudo-members are not created until needed, but are then added to the Enum so that Weekday(3) is Weekday(3) is True (i.e. pseudo-members are also singletons).

    One solution is to have __contains__ attempt a value -> member conversion so any pseudo-members are created.

  7. removed
    pendingThe issue will be closed if no feedback is provided
    on Mar 10, 2025
  8. added a commit that references this issue on Mar 12, 2025
  9. added a commit that references this issue on Mar 12, 2025
  10. added a commit that references this issue on Mar 17, 2025
  11. added a commit that references this issue on Mar 23, 2025
  12. picnixz commented on Mar 23, 2025

    @picnixz
    Member

    @ethanfurman What do you want to do with the backports? 3.12 is nearing its last bug fix so I wonder whether you are willing to review my PR (which had conflicts that I resolved but I'm not sure it's entirely correct for you)

  13. ethanfurman commented on Mar 23, 2025

    @ethanfurman
    Member

    Thanks for taking care of that; my PC has been having issues this last week.

  14. added a commit that references this issue on Mar 24, 2025
  15. picnixz commented on Mar 24, 2025

    @picnixz
    Member

    Thank you for the report and for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13only security fixes3.14bugs and security fixesstdlibStandard 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