[Python-checkins] [3.8] bpo-37479: Enum - use correct __format__ (GH-14545)

Ethan Furman webhook-mailer at python.org
Tue Sep 15 10:01:27 EDT 2020


https://github.com/python/cpython/commit/38c8d3930eb872258a82339bcba3bce1d0e3ac2c
commit: 38c8d3930eb872258a82339bcba3bce1d0e3ac2c
branch: 3.8
author: Ethan Furman <ethan at stoneleaf.us>
committer: GitHub <noreply at github.com>
date: 2020-09-13T13:47:43-07:00
summary:

[3.8] bpo-37479: Enum - use correct __format__ (GH-14545)

* bpo-37479: on Enum subclasses with mixins, __format__ uses overridden __str__.
(cherry picked from commit 2f19e82fbe98ce86bcd98a176328af2808b678e8)

Co-authored-by: thatneat <thatneat at users.noreply.github.com>

files:
A Misc/NEWS.d/next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst
M Doc/library/enum.rst
M Lib/enum.py
M Lib/test/test_enum.py
M Misc/ACKS

diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index ff1510cdd0e41..03aa2f1b21655 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -744,9 +744,11 @@ Some rules:
    :meth:`__str__` and :meth:`__repr__` respectively; other codes (such as
    `%i` or `%h` for IntEnum) treat the enum member as its mixed-in type.
 5. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
-   and :func:`format` will use the mixed-in
-   type's :meth:`__format__`.  If the :class:`Enum` class's :func:`str` or
-   :func:`repr` is desired, use the `!s` or `!r` format codes.
+   and :func:`format` will use the mixed-in type's :meth:`__format__`
+   unless :meth:`__str__` or :meth:`__format__` is overridden in the subclass,
+   in which case the overridden methods or :class:`Enum` methods will be used.
+   Use the !s and !r format codes to force usage of the :class:`Enum` class's
+   :meth:`__str__` and :meth:`__repr__` methods.
 
 When to use :meth:`__new__` vs. :meth:`__init__`
 ------------------------------------------------
diff --git a/Lib/enum.py b/Lib/enum.py
index 14cc00e783915..16033b1c6fac5 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -633,8 +633,9 @@ def __format__(self, format_spec):
         # we can get strange results with the Enum name showing up instead of
         # the value
 
-        # pure Enum branch
-        if self._member_type_ is object:
+        # pure Enum branch, or branch with __str__ explicitly overridden
+        str_overridden = type(self).__str__ != Enum.__str__
+        if self._member_type_ is object or str_overridden:
             cls = str
             val = str(self)
         # mix-in branch
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index a2a3c56734901..0f91f00d92dd9 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -445,12 +445,63 @@ def test_format_enum(self):
         self.assertEqual('{:<20}'.format(Season.SPRING),
                          '{:<20}'.format(str(Season.SPRING)))
 
-    def test_format_enum_custom(self):
+    def test_str_override_enum(self):
+        class EnumWithStrOverrides(Enum):
+            one = auto()
+            two = auto()
+
+            def __str__(self):
+                return 'Str!'
+        self.assertEqual(str(EnumWithStrOverrides.one), 'Str!')
+        self.assertEqual('{}'.format(EnumWithStrOverrides.one), 'Str!')
+
+    def test_format_override_enum(self):
+        class EnumWithFormatOverride(Enum):
+            one = 1.0
+            two = 2.0
+            def __format__(self, spec):
+                return 'Format!!'
+        self.assertEqual(str(EnumWithFormatOverride.one), 'EnumWithFormatOverride.one')
+        self.assertEqual('{}'.format(EnumWithFormatOverride.one), 'Format!!')
+
+    def test_str_and_format_override_enum(self):
+        class EnumWithStrFormatOverrides(Enum):
+            one = auto()
+            two = auto()
+            def __str__(self):
+                return 'Str!'
+            def __format__(self, spec):
+                return 'Format!'
+        self.assertEqual(str(EnumWithStrFormatOverrides.one), 'Str!')
+        self.assertEqual('{}'.format(EnumWithStrFormatOverrides.one), 'Format!')
+
+    def test_str_override_mixin(self):
+        class MixinEnumWithStrOverride(float, Enum):
+            one = 1.0
+            two = 2.0
+            def __str__(self):
+                return 'Overridden!'
+        self.assertEqual(str(MixinEnumWithStrOverride.one), 'Overridden!')
+        self.assertEqual('{}'.format(MixinEnumWithStrOverride.one), 'Overridden!')
+
+    def test_str_and_format_override_mixin(self):
+        class MixinWithStrFormatOverrides(float, Enum):
+            one = 1.0
+            two = 2.0
+            def __str__(self):
+                return 'Str!'
+            def __format__(self, spec):
+                return 'Format!'
+        self.assertEqual(str(MixinWithStrFormatOverrides.one), 'Str!')
+        self.assertEqual('{}'.format(MixinWithStrFormatOverrides.one), 'Format!')
+
+    def test_format_override_mixin(self):
         class TestFloat(float, Enum):
             one = 1.0
             two = 2.0
             def __format__(self, spec):
                 return 'TestFloat success!'
+        self.assertEqual(str(TestFloat.one), 'TestFloat.one')
         self.assertEqual('{}'.format(TestFloat.one), 'TestFloat success!')
 
     def assertFormatIsValue(self, spec, member):
diff --git a/Misc/ACKS b/Misc/ACKS
index a08e917b30765..e6d0c3ba12708 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -364,6 +364,7 @@ Raúl Cumplido
 Antonio Cuni
 Brian Curtin
 Hakan Celik
+Jason Curtis
 Paul Dagnelie
 Lisandro Dalcin
 Darren Dale
diff --git a/Misc/NEWS.d/next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst b/Misc/NEWS.d/next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst
new file mode 100644
index 0000000000000..cf23155c33958
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst
@@ -0,0 +1,2 @@
+When `Enum.__str__` is overridden in a derived class, the override will be
+used by `Enum.__format__` regardless of whether mixin classes are present.
\ No newline at end of file



More information about the Python-checkins mailing list