Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
timrid opened this issue Mar 10, 2025 · 3 comments
Open

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

timrid opened this issue Mar 10, 2025 · 3 comments
Labels
3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error

Comments

@timrid
Copy link

timrid commented Mar 10, 2025

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

@timrid timrid added the type-bug An unexpected behavior, bug, or error label Mar 10, 2025
@picnixz picnixz added stdlib Python modules in the Lib dir 3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes labels Mar 10, 2025
@picnixz
Copy link
Member

picnixz commented Mar 10, 2025

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)

@picnixz picnixz added the pending The issue will be closed if no feedback is provided label Mar 10, 2025
@tomasr8
Copy link
Member

tomasr8 commented Mar 10, 2025

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?

@ethanfurman
Copy link
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.

@ethanfurman ethanfurman removed the pending The issue will be closed if no feedback is provided label Mar 10, 2025
miss-islington pushed a commit to miss-islington/cpython that referenced this issue Mar 12, 2025
…pythonGH-131053)

Check would fail if value would create a pseudo-member, but that member
had not yet been created.  We now attempt to create a pseudo-member for
a passed-in value first.
(cherry picked from commit 17d06ae)

Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
Co-authored-by: Tomas R. <tomas.roun8@gmail.com>
ethanfurman added a commit that referenced this issue Mar 12, 2025
…31053)

Check would fail if value would create a pseudo-member, but that member
had not yet been created.  We now attempt to create a pseudo-member for
a passed-in value first.

Co-authored-by: Tomas R. <tomas.roun8@gmail.com>
plashchynski pushed a commit to plashchynski/cpython that referenced this issue Mar 17, 2025
…pythonGH-131053)

Check would fail if value would create a pseudo-member, but that member
had not yet been created.  We now attempt to create a pseudo-member for
a passed-in value first.

Co-authored-by: Tomas R. <tomas.roun8@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes stdlib Python modules in the Lib dir type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

4 participants