Closed
Description
@proelke made me aware about a changes to Python 3.11 that introduce subtle changes to all enums that subclass from both str
and enum.Enum
. Practically all enums defined in this library are subclassed from these 2 classes.
Consider the following code:
import enum
class ResetType(str, enum.Enum):
hard = "hard"
soft = "soft"
# Passes in both Python 3.11 and earlier.
assert ResetType.hard == "hard"
# Passes in Python 3.10 and below, but fails in Python 3.11.
assert f'{ResetType.hard}' == "hard"
$ python3.11 /enum_test.py
Traceback (most recent call last):
File "/tmp/enum_test.py", line 15, in <module>
assert f'{ResetType.hard}' == "hard"
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
The underlying cause is that Python 3.11 changes the behavior of enum.Enum.__format__()
slightly. f-strings
(and a few other string formatting methods) rely on dunder method __format___()
.
So far, I considered two solutions.
Solution 1: StrEnum
Python 3.11 introduces enum.StrEnum
. One could implement a solution as described at https://tomwojcik.com/posts/2023-01-02/python-311-str-enum-breaking-change
try:
# breaking change introduced in python 3.11
from enum import StrEnum
except ImportError: # pragma: no cover
from enum import Enum # pragma: no cover
class StrEnum(str, Enum): # pragma: no cover
pass # pragma: no cover
class ResetType(StrEnum):
hard = "hard"
soft = "soft"
# Passes in both Python 3.11 and earlier.
assert ResetType.hard == "hard"
# Passes now in Python 3.10 and below and Python 3.11!
assert f'{ResetType.hard}' == "hard"
Manually implement __format__()
The other solution is to implement __format__()
manually.
import enum
class _Enum(str, enum.Enum):
def __format__(self, spec) -> str:
return str.__format__(str(self.name), spec)
class ResetType(_Enum):
hard = "hard"
soft = "soft"
# Passes in both Python 3.11 and earlier.
assert ResetType.hard == "hard"
# Passes now in Python 3.10 and below and Python 3.11!
assert f"{ResetType.hard}" == "hard"
See also