classification
Title: enum repr should use __qualname__
Type: enhancement Stage: patch review
Components: Library (Lib) Versions: Python 3.8
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: ethan.furman Nosy List: barry, danishprakash, doerwalter, eli.bendersky, enedil, ethan.furman, orlnub123, serhiy.storchaka, underscore_asterisk
Priority: normal Keywords: easy, patch

Created on 2018-08-20 16:34 by doerwalter, last changed 2019-07-18 18:37 by ethan.furman.

Pull Requests
URL Status Linked Edit
PR 8989 closed danishprakash, 2018-08-29 09:39
PR 9011 closed orlnub123, 2018-08-30 23:15
PR 14809 merged doerwalter, 2019-07-17 10:23
Messages (15)
msg323799 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2018-08-20 16:34
The __repr__ output of an enum class should use __qualname__ instead of __name__. The following example shows the problem:

import enum

class X:
   class I:
      pass

class Y:
   class I(enum.Enum):
      pass

print(X.I)
print(Y.I)

This prints:

<class '__main__.X.I'>
<enum 'I'>

I would have expected it to print

<class '__main__.X.I'>
<enum 'Y.I'>

or even for maximum consistency

<class '__main__.X.I'>
<enum '__main__.Y.I'>
msg323801 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-08-20 17:15
__qualname__ should be used only together with __module__.

I agree that the repr of enum should be more consistent with the repr of class.
msg323822 - (view) Author: (underscore_asterisk) * Date: 2018-08-21 06:27
Hi,
I would like to work on this issue. I can submit a PR by tomorrow (or maybe even later today).
msg324110 - (view) Author: Michał Radwański (enedil) * Date: 2018-08-26 00:50
Serhiy, would it be ok to put '__module__' + '.' + __qualname__ here?
msg324313 - (view) Author: Danish Prakash (danishprakash) * Date: 2018-08-29 09:41
Serhiy, why should __qualname__ always be used together with __module__? I can't seem to find a valid reason, I've been through the pep.

<made a PR due to inactivity>
msg324402 - (view) Author: (orlnub123) * Date: 2018-08-30 23:22
I've created a separate PR that also changes the __str__s.
msg324442 - (view) Author: (orlnub123) * Date: 2018-08-31 19:54
After some thinking I've come to the conclusion that making the __str__s use the fully qualified name was a bad idea. I've closed my PR.
msg325006 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-09-11 14:03
> Serhiy, why should __qualname__ always be used together with __module__?

Because what is the purpose of using __qualname__?
msg325007 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-09-11 14:18
I think using more longer name in repr and/or str for *instances* of enum classes is not good idea. They are already verbose, and this will make them more verbose.

Actually in some cases when enum instances are exposed as module globals, I would want to make them including the module name instead of the enum class name. For example:

>>> import socket
>>> socket.AF_UNIX
<AddressFamily.AF_UNIX: 1>
>>> print(socket.AF_UNIX)
AddressFamily.AF_UNIX

"socket.AF_UNIX" instead of "AddressFamily.AF_UNIX" would look better to me.
msg325020 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-11 16:00
Serhiy said:
-----------
> I think using more longer name in repr and/or str for *instances* of
> enum classes is not good idea. They are already verbose, and this
> will make them more verbose.

I'm okay with verbose reprs, as debugging is the primary feature for those (at least for me).  I'm not okay with __str__ being other than what it is now (but see below).

Serhiy also said:
----------------
> Actually in some cases when enum instances are exposed as module
> globals, I would want to make them including the module name instead
> of the enum class name. For example:
> 
> >>> import socket
> >>> socket.AF_UNIX
> <AddressFamily.AF_UNIX: 1>
> >>> print(socket.AF_UNIX)
> AddressFamily.AF_UNIX
>
> "socket.AF_UNIX" instead of "AddressFamily.AF_UNIX" would look better
> to me.

Since AddressFamily, and other stdlib converted constants, are created user `_convert`, I have no problem with that method also changing the __str__ to be `module.member' instead.
msg325023 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2018-09-11 16:34
Okay, I might be changing my mind.  In most cases I suspect the difference would be minimal, but when it isn't, it really isn't.  Take this example from a pydoc test:

  class Color(enum.Enum)
   |  Color(value, names=None, *, module=None, qualname=None, type=None, start=1)
   |  
   |  An enumeration.
   |  
   |  Method resolution order:
   |      Color
   |      enum.Enum
   |      builtins.object
   |  
   |  Data and other attributes defined here:
   |  
-  |  blue = <test.test_enum.TestStdLib.Color.blue: 3>
+  |  blue = <Color.blue: 3>
   |  
-  |  green = <test.test_enum.TestStdLib.Color.green: 2>
+  |  green = <Color.green: 2>
   |  
-  |  red = <test.test_enum.TestStdLib.Color.red: 1>
+  |  red = <Color.red: 1>

It feels like the important information is completely lost in the noise.

Okay, I'm rejecting the __repr__ changes.  Besides the potential verbosity, there should usually only be one of any particular Enum, __module__ and __qualname__ are both readily available when there are more than one (either on accident or by design), and users can modify their own __repr__s if they like.

I'm still thinking about the change in _convert_ to modify __str__ to use the module name instead of the class name....  Here are my questions about that:

- Modify just __str__ or __repr__ as well?
    socket.AF_UNIX instead of AddressFamily.AF_UNIX
    <socket.AddressFamily.AF_UNIX: 1> instead of <AddressFamily.AF_UNIX: 1>

- potential confusion that actual instances of Enum in the stdlib appear
  differently than "regular" Enums?  Or perhaps call out those differences
  in the documentation as examples of customization?
msg348014 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2019-07-16 12:03
Can we at least get the __qualname__ in exception messages?

Currently enum.Enum.__new__() and enum.Enum._missing_() use:

   raise ValueError("%r is not a valid %s" % (value, cls.__name__))

IMHO this should be:

   raise ValueError("%r is not a valid %s" % (value, cls.__qualname__))

in both spots.

Example code:

class Person:
    class Type(enum.Enum):
        EMPLOYEE = "employee"
        CUSTOMER = "customer"
        SUPPLIER = "supplier"

class Contact:
    class Type(enum.Enum):
        EMAIL = "email"
        PHONE = "phone"
        MOBILE = "mobile"

with this the following code:

    Person.Type('foo')

raises the exception:

    ValueError: 'foo' is not a valid Type

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/enum.py", line 310, in __call__
        return cls.__new__(cls, value)
      File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/enum.py", line 564, in __new__
        raise exc
      File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/enum.py", line 548, in __new__
        result = cls._missing_(value)
      File "/usr/local/Cellar/python/3.7.4/Frameworks/Python.framework/Versions/3.7/lib/python3.7/enum.py", line 577, in _missing_
        raise ValueError("%r is not a valid %s" % (value, cls.__name__))
    ValueError: 'foo' is not a valid Type

IMHO the exception message:

    ValueError: 'foo' is not a valid Person.Type

would be much more helpful and unambiguous.

And BTW, maybe we should suppress exception chaining here, i.e. use:

   raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) from None
msg348026 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2019-07-16 16:01
Both are good suggestions.  The first should definitely happen.  I'll investigate the second.
msg348048 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2019-07-17 03:11
If someone would like to make a PR for using __qualname__ in error messages that would be great.  :)
msg348123 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2019-07-18 18:37
New changeset 323842c2792a81e87917790506ec3457832c84b3 by Ethan Furman (Walter Dörwald) in branch 'master':
bpo-34443: Use __qualname__ instead of __name__ in enum exception messages. (GH-14809)
https://github.com/python/cpython/commit/323842c2792a81e87917790506ec3457832c84b3
History
Date User Action Args
2019-07-18 18:37:17ethan.furmansetmessages: + msg348123
2019-07-17 10:23:16doerwaltersetstage: needs patch -> patch review
pull_requests: + pull_request14603
2019-07-17 03:11:46ethan.furmansetmessages: + msg348048
stage: test needed -> needs patch
2019-07-16 16:01:20ethan.furmansetstatus: closed -> open
resolution: rejected ->
messages: + msg348026

stage: resolved -> test needed
2019-07-16 12:03:41doerwaltersetmessages: + msg348014
2019-07-16 03:20:42ethan.furmansetstatus: open -> closed
resolution: rejected
stage: patch review -> resolved
2018-09-11 16:34:16ethan.furmansetmessages: + msg325023
2018-09-11 16:00:40ethan.furmansetmessages: + msg325020
2018-09-11 14:18:14serhiy.storchakasetmessages: + msg325007
2018-09-11 14:03:43serhiy.storchakasetmessages: + msg325006
2018-08-31 19:54:40orlnub123setmessages: + msg324442
2018-08-30 23:22:46orlnub123setnosy: + orlnub123
messages: + msg324402
2018-08-30 23:15:27orlnub123setpull_requests: + pull_request8481
2018-08-29 09:41:49danishprakashsetnosy: + danishprakash
messages: + msg324313
2018-08-29 09:39:35danishprakashsetkeywords: + patch
stage: patch review
pull_requests: + pull_request8462
2018-08-26 00:50:14enedilsetnosy: + enedil
messages: + msg324110
2018-08-21 06:27:59underscore_asterisksetnosy: + underscore_asterisk
messages: + msg323822
2018-08-20 17:39:51ethan.furmansetassignee: ethan.furman
2018-08-20 17:15:59serhiy.storchakasetnosy: + barry, eli.bendersky, serhiy.storchaka, ethan.furman

messages: + msg323801
versions: + Python 3.8
2018-08-20 16:35:45doerwaltersetkeywords: + easy
2018-08-20 16:34:28doerwaltercreate