Notice: While JavaScript is not essential for this website, your interaction with the content will be limited. Please turn JavaScript on for the full experience.

PEP 663 -- Improving and Standardizing Enum str(), repr(), and format() behaviors

PEP:663
Title:Improving and Standardizing Enum str(), repr(), and format() behaviors
Author:Ethan Furman <ethan at stoneleaf.us>
Discussions-To:python-dev at python.org
Status:Draft
Type:Informational
Created:23-Feb-2013
Python-Version:3.11
Post-History:20-Jul-2021
Resolution:

Abstract

Now that we have a few years experience with Enum usage it is time to update the repr(), str(), and format() of the various enumerations by their intended purpose.

Motivation

The addition of StrEnum with its requirement to have its str() be its value is inconsistent with other provided Enum's str.

Having the str() of IntEnum and IntFlag not be the value causes bugs and extra work when replacing existing constants.

Having the str() and format() of an enum member be different can be confusing.

The iteration of Flag members, which directly affects their repr(), is inelegant at best, and buggy at worst.

Rationale

Enums are becoming more common in the standard library; being able to recognize enum members by their repr(), and having that repr() be easy to parse, is useful and can save time and effort in understanding and debugging code.

However, the enums with mixed-in data types (IntEnum, IntFlag, and the new StrEnum) need to be more backwards compatible with the constants they are replacing -- specifically, str(replacement_enum_member) == str(original_constant) should be true (and the same for format()).

IntEnum, IntFlag, and StrEnum should be as close to a drop-in replacement of existing integer and string constants as is possible. Towards that goal, the str() output of each should be its inherent value; e.g. if Color is an IntEnum:

>>> Color.RED
<Color.RED: 1>
>>> str(Color.RED)
'1'
>>> format(Color.RED)
'1'

Note that format() already produces the correct output in 3.10, only str() needs updating.

As much as possible, the str()`, ``repr(), and format() of enum members should be standardized across the stardard library.

The repr() of Flag currently includes aliases, which it should not; fixing that will, of course, already change its repr() in certain cases.

Specification

There a three broad categories of enum usage:

  • standard: Enum or Flag a new enum class is created, and the members are used as class.member_name
  • drop-in replacement: IntEnum, IntFlag, StrEnum a new enum class is created which also subclasses int or str and uses int.__str__ or str.__str__; the repr can be changed by using the global_enum decorator
  • user-mixed enums and flags the user creates their own integer-, float-, str-, whatever-enums instead of using enum.IntEnum, etc.

Some sample enums:

# module: tools.py

class Hue(Enum):  # or IntEnum
    LIGHT = -1
    NORMAL = 0
    DARK = +1

class Color(Flag):  # or IntFlag
    RED = 1
    GREEN = 2
    BLUE = 4

class Grey(int, Enum):  # or (int, Flag)
   BLACK = 0
   WHITE = 1

Using the above enumerations, the following table shows the old and new behavior, while the last shows the final result:

type enum repr() enum str() enum format() flag repr() flag str() flag format()
standard 3.10       <Color.RED|GREEN: 3> Color.RED|GREEN Color.RED|GREEN
new       <Color(3): RED|GREEN> Color.RED|Color.GREEN Color.RED|Color.GREEN
               
user mixed 3.10     1 <Grey.WHITE: 1>   1
new     Grey.WHITE <Grey(1): WHITE>   Grey.WHITE
               
int drop-in 3.10   Hue.LIGHT   <Color.RED|GREEN: 3> Color.RED|GREEN  
new   -1   <Color(3): RED|GREEN> 3  
               
global 3.10 <Hue.LIGHT: -1> Hue.LIGHT Hue.LIGHT <Color.RED|GREEN: 3> Color.RED|GREEN Color.RED|GREEN
new tools.LIGHT LIGHT LIGHT tools.RED|tools.GREEN RED|GREEN RED|GREEN
               
user mixed 3.10 <Grey.WHITE: 1 Grey.WHITE Grey.WHITE <Grey.WHITE: 1> Grey.WHITE 1
new tools.WHITE WHITE WHITE tools.WHITE WHITE WHITE
               
int drop-in 3.10 <Hue.LIGHT: -1> Hue.LIGHT   <Color.RED|GREEN: 3> Color.RED|GREEN  
new tools.LIGHT -1   tools.RED|tools.GREEN 3  

Which will result in:

type enum repr() enum str() enum format() flag repr() flag str() flag format()
standard <Hue.LIGHT: -1> Hue.LIGHT Hue.LIGHT <Color(3): RED|GREEN> Color.RED|Color.GREEN Color.RED|Color.GREEN
user mixed <Grey.WHITE: 1> Grey.WHITE Grey.WHITE <Grey(1): WHITE> Grey.WHITE Grey.WHITE
int drop-in <Hue.LIGHT: -1> -1 -1 <Color(3): RED|GREEN> 3 3
global tools.LIGHT LIGHT LIGHT tools.RED|tools.GREEN RED|GREEN RED|GREEN
user mixed tools.WHITE WHITE WHITE tools.WHITE WHITE WHITE
int drop-in tools.LIGHT -1 -1 tools.RED|tools.GREEN 3 3

As can be seen, repr() is primarily affected by whether the members are global, while str() is affected by being global or by being a drop-in replacement, with the drop-in replacement status having a higher priority. Also, the basic repr() and str() have changed for flags as the old style was very clunky.

The repr() for Enum vs Flag are different, primarily because the Enum repr() does not work well for flags. I like being able to tell whether an enum member is a Flag or an Enum based on the repr() alone, but am open to arguments for changing Enum's repr() to match Flag's.

Backwards Compatibility

My understanding is that str() and repr() output has much lower backwards compatibility requirements. Even so, I expect the majority of breakage to be in doc and unit tests. I'm less clear on the policy for format().

Note that by changing the str() of the drop-in category, we will actually prevent future breakage when IntEnum, et al, are used to replace existing constants.

Mitigation

Normal usage of enum members will not change: re.ASCII can still be used as re.ASCII and will still compare equal to 256. If one wants their own enums in their own code to remain the same they will need to write their own base Enum class and then write the appropriate repr, str(), and format() methods (or copy them from the 3.10 enum module).

Source: https://github.com/python/peps/blob/master/pep-0663.txt