classification
Title: Enum: modify __repr__, __str__; update docs
Type: enhancement Stage:
Components: Library (Lib) Versions: Python 3.10
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: ethan.furman Nosy List: The Compiler, barry, eli.bendersky, ethan.furman, ezio.melotti, mrabarnett, rhettinger, scoder, serhiy.storchaka, veky
Priority: normal Keywords: patch

Created on 2020-03-25 19:48 by ethan.furman, last changed 2021-04-13 16:44 by ethan.furman.

Pull Requests
URL Status Linked Edit
PR 22392 merged ethan.furman, 2020-09-24 04:55
PR 25116 closed ethan.furman, 2021-03-31 15:10
PR 25118 merged ethan.furman, 2021-03-31 15:36
Messages (13)
msg365019 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-03-25 19:48
Serhiy had the idea of having Enum._convert also modify the __str__ and __repr__ of newly created enumerations to display the module name instead of the enumeration name (https://bugs.python.org/msg325007):

--> socket.AF_UNIX
<AddressFamily.AF_UNIX: 1>   ==>  <socket.AF_UNIX: 1>

--> print(socket.AF_UNIX)
AddressFamily.AF_UNIX        ==>  socket.AF_UNIX

Thoughts?
msg366190 - (view) Author: Vedran Čačić (veky) * Date: 2020-04-11 03:49
> _in some cases when enum instances are exposed as module globals_

Yes. And repr should be inverse of eval, but it's probably too late for that. :-/
msg376900 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-09-14 20:26
Looks like the `re` module's flags have been updated separately in issue36548:

  >>> import re
  >>> re.I
  re.IGNORECASE

  >>> print(re.I)
  # should also be re.IGNORECASE

  >>> re.I|re.S|re.X
  re.IGNORECASE|re.DOTALL|re.VERBOSE


For stdlib Enum conversions are we happy with that?  Or should __str__ just print the numeric value?
msg376913 - (view) Author: Vedran Čačić (veky) * Date: 2020-09-14 22:31
If it's considered to be not too backwards-incompatible, I think it would be nice to have str different from repr. That way we can finetune what exactly we need. But we can already do almost exactly that with *int* instead of *str*, so it's not too compelling.

Much more important thing is the "repr as inverse of eval". Is there any way we can have that for our own enums (as a mixin or a decorator)?

    @module_global(re)
    class RegexFlag(Enum):
        ...

It would be fantastic. :-)
msg376915 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-09-14 23:04
"repr as inverse of eval" is nice to have, but it is not a requirement.
msg376918 - (view) Author: Vedran Čačić (veky) * Date: 2020-09-15 00:09
Noone said it is a requirement, I just said it would be nice to have it factored out as a decorator or something instead of having to write __repr__ over and over again.
msg377495 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-09-25 18:16
At this point, the PR has made the following changes:

- normal Enums
  - repr() -> "classname.membername"
  - str()  -> "membername"

- stdlib Enums available as module attributes (RegexFlag, AddressFamily, etc.)
  - repr() -> "modulename.membername"
  - str()  -> "membername"
msg378019 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2020-10-05 12:22
Python-Dev thread [0], summary below:

> As you may have noticed, Enums are starting to pop up all
> over the stdlib [1].
>
> To facilitate transforming existing module constants to
> IntEnums there is `IntEnum._convert_`.  In Issue36548 [2]
> Serhiy modified the __repr__ of RegexFlag:
>
>   >>> import re
>   >>> re.I
>   re.IGNORECASE
>
> I think for converted constants that that looks nice.
>  For anyone that wants the actual value, it is of course
> available as the `.value` attribute:
>
>   >>> re.I.value
>   2
>
> I'm looking for arguments relating to:
>
> - should _convert_ make the default __repr__ be
>   module_name.member_name?
>
> - should _convert_ make the default __str__ be the same,
>   or be the numeric value?

After discussions with Guido I made a (largely done) PR [3] which:

for stdlib global constants (such as RE)
   - repr() -> uses `module.member_name`
   - str() -> uses `member_name`

for stdlib non-global constants, and enums in general
   - repr() -> uses `class.member_name`
   - str() -> uses `member_name`

The questions I would most appreciate an answer to at this point:

- do you think the change has merit?
- why /shouldn't/ we make the change?

As a reminder, the underlying issue is trying to keep at least the stdlib Enum representations the same for those that are replacing preexisting constants.


[0] https://mail.python.org/archives/list/python-dev@python.org/message/CHQW6THTDYNPPFWQ2KDDTUYSAJDCZFNP/

[1] I'm working on making their creation faster.  If anyone wanted to convert EnumMeta to C I would be grateful.

[2] https://bugs.python.org/issue36548

[3] https://github.com/python/cpython/pull/22392
msg378239 - (view) Author: Vedran Čačić (veky) * Date: 2020-10-08 11:58
> - do you think the change has merit?

Absolutely.

> - why /shouldn't/ we make the change?

Well, standard backward compatibility stuff. :-)
msg389869 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-03-31 04:17
New changeset b775106d940e3d77c8af7967545bb9a5b7b162df by Ethan Furman in branch 'master':
bpo-40066: Enum: modify `repr()` and `str()` (GH-22392)
https://github.com/python/cpython/commit/b775106d940e3d77c8af7967545bb9a5b7b162df
msg390895 - (view) Author: Stefan Behnel (scoder) * (Python committer) Date: 2021-04-12 20:07
> why /shouldn't/ we make the change?

It breaks doctests, and probably some other unit tests, too, e.g. for output formatting.

What should we suggest users to do? Replace

    >>> get_flag(…)
    <app_enums.TrickyFlag: 1>

by

    >>> get_flag(…) == app_enums.TrickyFlag  or get_flag(…)  # (also show result on failure)
    True

and

    assertEqual(
        "You caught the %s flag!" % result,
        "You caught the app_enums.TrickyFlag flag!")

by

    assertEqual(
        ("You caught the %s flag!" % result).replace("app_enums.", ""),
        "You caught the TrickyFlag flag!")

?

Note that using "%r" does not help, since it's also backwards incompatible.

For their own enums, users can probably backport (or forward-port) "__str__()" and "__repr__()" themselves in order to work around this difference. But it's something they would have to do.

I certainly understand the reasoning, and it also makes new Py3.10-only doctests nicer, actually, but IMHO it counts as deliberate breakage.
msg390956 - (view) Author: Florian Bruhin (The Compiler) * Date: 2021-04-13 11:44
I'm probably a bit late to the party as well, but as some additional datapoints:

- This broke the tests for my project and I ended up adding a wrapper to stringify enums (since I actually prefer having the enum type in debug logs and such): https://github.com/qutebrowser/qutebrowser/commit/e2c5fe6262564d9d85806bfa9d4486a411cf5045
- This broke tests for pytest, and also changes its test IDs when enums are used for test parametrization, which might cause more breakage downstream: https://github.com/pytest-dev/pytest/issues/8546
msg390976 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2021-04-13 16:44
Thank you for the feedback.  

The new str() and repr() make more sense for Flag-based enumerations, and I'm hesitant to have different formats for Enum- vs Flag-based enums.

Would it be helpful to have another base Enum class to derive from that maintained the original str() and repr() formats?
History
Date User Action Args
2021-04-13 16:44:05ethan.furmansetstatus: closed -> open
resolution: fixed ->
messages: + msg390976

stage: resolved ->
2021-04-13 11:44:58The Compilersetmessages: + msg390956
2021-04-12 20:07:42scodersetnosy: + scoder
messages: + msg390895
2021-04-08 18:38:41ethan.furmansetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2021-04-08 07:53:41The Compilersetnosy: + The Compiler
2021-03-31 15:36:39ethan.furmansetpull_requests: + pull_request23862
2021-03-31 15:10:51ethan.furmansetpull_requests: + pull_request23861
2021-03-31 04:17:40ethan.furmansetmessages: + msg389869
2021-03-26 04:13:17ethan.furmansetcomponents: + Library (Lib)
title: Enum._convert should change __repr__ and/or __str__ to use module name instead of class name -> Enum: modify __repr__, __str__; update docs
2020-10-08 11:58:12vekysetmessages: + msg378239
2020-10-05 12:22:52ethan.furmansetnosy: + rhettinger
messages: + msg378019
2020-09-25 18:16:58ethan.furmansetmessages: + msg377495
2020-09-24 04:55:47ethan.furmansetkeywords: + patch
stage: patch review
pull_requests: + pull_request21433
2020-09-15 00:09:03vekysetmessages: + msg376918
2020-09-14 23:04:13ethan.furmansetmessages: + msg376915
2020-09-14 22:31:43vekysetmessages: + msg376913
2020-09-14 20:26:29ethan.furmansetnosy: + ezio.melotti, mrabarnett, serhiy.storchaka

messages: + msg376900
versions: + Python 3.10, - Python 3.9
2020-04-11 03:49:21vekysetnosy: + veky
messages: + msg366190
2020-03-25 19:48:58ethan.furmancreate