Skip to content

enum.CONFORM behavior breaks backwards compatibility #103365

Description

@benburrill

Encountered yet another breaking change for enums. There are two problems here:

  • CONFORM doesn't "conform" to members that are partially unsupported
  • CONFORM does not match the previous behavior for Flag. It used to be more similar to STRICT, but STRICT also doesn't allow partially unsupported members, while Flag in 3.10 did.

Example to reproduce:

import enum
class SkipFlag(enum.Flag):
    A = 1
    B = 2
    C = 4 | B

print(SkipFlag.C in (SkipFlag.A|SkipFlag.C))

class SkipIntFlag(enum.IntFlag):
    A = 1
    B = 2
    C = 4 | B

print(SkipIntFlag.C in (SkipIntFlag.A|SkipIntFlag.C))

print(SkipIntFlag(42).value)
print(SkipFlag(42).value)

In Python 3.10.6, this code outputs:

True
True
42
Traceback (most recent call last):
...
ValueError: 42 is not a valid SkipFlag

In Python 3.11.2:

False
True
42
2

Having such members as C is useful to create a flags that are distinct from B but always implies B.

In a previous comment bug report it was stated that the previous behavior of Flag was CONFORM:

Actually, won't need a deprecation period, since the proper default for Flag is CONFORM as that matches previous behavior.

Originally posted by @ethanfurman in #96865 (comment)

Clearly this is incorrect. The previous behavior was more similar to STRICT, but there appears to be no precise equivalent in Python 3.11 to the old behavior. Using STRICT in Python 3.11, SkipFlag.A | SkipFlag.C would fail to be created completely.

Linked PRs

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Apr 8, 2023
  2. sunmy2019 commented on Apr 8, 2023

    @sunmy2019
    Member

    With #24215, only single-bit values will be considered in Flag when using bitwise ops like &, |, ^, ~. Multi-bit values are only considered an alias.

    Thus SkipIntFlag.A | SkipIntFlag.C is actually SkipIntFlag.A | SkipIntFlag.B, since the 0b100 bit is discarded. But __contains__ will only consider its underlying values.

    You will need an element with value 4 inside of SkipFlag.

    This answers "why it behaves like this". Related Issues: #82431

  3. sunmy2019 commented on Apr 8, 2023

    @sunmy2019
    Member

    Only single-bit values will be considered in Flag

    This is documented with an inconspicuous line.

    https://docs.python.org/3.10/library/enum.html#combining-members-of-flag
    https://docs.python.org/3.11/howto/enum.html#flag-and-intflag-minutia

    I think this should be added to the API reference here:
    https://docs.python.org/3/library/enum.html#enum.Flag

  4. benburrill commented on Apr 8, 2023

    @benburrill
    Author

    https://docs.python.org/3.11/howto/enum.html#flag-and-intflag-minutia

    It's also documented there that the default for Flag in Python 3.11 is STRICT, but it's really CONFORM

  5. moved this to In Progress in enum issueson Apr 12, 2023
  6. added a commit that references this issue on Apr 13, 2023
  7. added 2 commits that reference this issue on Apr 13, 2023
  8. added a commit that references this issue on Apr 13, 2023
  9. ethanfurman commented on Apr 13, 2023

    @ethanfurman
    Member

    @benburrill Thank you for the bug report. STRICT has been restored as the default for Flag, and the underlying issues fixed.

  10. moved this from In Progress to Done in enum issueson Apr 13, 2023
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