From 60ee8a6fdef7e272e5bfcbfeee22ea439ca64cc8 Mon Sep 17 00:00:00 2001 From: thatneat Date: Mon, 1 Jul 2019 22:36:09 -0700 Subject: [PATCH 1/7] bpo-37479: on Enum subclasses with mixins, __format__ uses overridden __str__ --- Doc/library/enum.rst | 8 +++++--- Lib/enum.py | 5 +++-- Lib/test/test_enum.py | 10 ++++++++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 19277d76995fed..feeef98c90da5e 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -739,9 +739,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 `, :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. + If the :class:`Enum` class's :func:`str` or :func:`repr` is desired, use the + `!s` or `!r` format codes. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ diff --git a/Lib/enum.py b/Lib/enum.py index 403f747d22a4ea..69a7f49d8072a4 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -622,8 +622,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 47081cf75ca086..fe4aeee919fd58 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -445,6 +445,16 @@ def test_format_enum(self): self.assertEqual('{:<20}'.format(Season.SPRING), '{:<20}'.format(str(Season.SPRING))) + def test_str_enum_custom(self): + class TestIntWithStrOverride(IntEnum): + one = 1 + two = 2 + def __str__(self): + return 'Overridden!' + + self.assertEqual(str(TestIntWithStrOverride.one), 'Overridden!') + self.assertEqual('{}'.format(TestIntWithStrOverride.one), 'Overridden!') + def test_format_enum_custom(self): class TestFloat(float, Enum): one = 1.0 From f46f951c8c48ad56ce2e478b0c5773c9dc1f03eb Mon Sep 17 00:00:00 2001 From: thatneat Date: Tue, 2 Jul 2019 12:41:45 -0700 Subject: [PATCH 2/7] More complete test coverage --- Lib/test/test_enum.py | 46 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index fe4aeee919fd58..0aab7cb5f3493d 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -445,22 +445,54 @@ def test_format_enum(self): self.assertEqual('{:<20}'.format(Season.SPRING), '{:<20}'.format(str(Season.SPRING))) - def test_str_enum_custom(self): - class TestIntWithStrOverride(IntEnum): - one = 1 - two = 2 + 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_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!') - self.assertEqual(str(TestIntWithStrOverride.one), 'Overridden!') - self.assertEqual('{}'.format(TestIntWithStrOverride.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_enum_custom(self): + 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): From 91fb2cd62827a5c5800ba8a4f505d19c94a09f17 Mon Sep 17 00:00:00 2001 From: thatneat Date: Tue, 2 Jul 2019 12:59:33 -0700 Subject: [PATCH 3/7] NEWS entry --- .../next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2019-07-02-12-43-57.bpo-37479.O53a5S.rst 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 00000000000000..cf23155c33958f --- /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 From e54d60d98acfc1e93dc9f49f32782e2feb855e2c Mon Sep 17 00:00:00 2001 From: thatneat Date: Tue, 2 Jul 2019 13:05:02 -0700 Subject: [PATCH 4/7] one more test to fill out the grid --- Lib/test/test_enum.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 0aab7cb5f3493d..3365ac561726e5 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -455,6 +455,15 @@ def __str__(self): self.assertEqual(str(EnumWithStrOverrides.one), 'Str!') self.assertEqual('{}'.format(EnumWithStrOverrides.one), 'Str!') + def test_format_override_enum(self): + class EnumWithFormatOverride(float, 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() From 89991afe694936cbb1b73295569d4d468023c0a0 Mon Sep 17 00:00:00 2001 From: thatneat Date: Wed, 3 Jul 2019 20:42:07 -0700 Subject: [PATCH 5/7] PR feedback: minor tweaks and ACKS entry --- Doc/library/enum.rst | 6 +++--- Lib/test/test_enum.py | 2 +- Misc/ACKS | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index feeef98c90da5e..022bdd6ce282ee 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -741,9 +741,9 @@ Some rules: 5. :ref:`Formatted string literals `, :meth:`str.format`, 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. - If the :class:`Enum` class's :func:`str` or :func:`repr` is desired, use the - `!s` or `!r` format codes. + 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 + :func:str and :func:repr methods. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 3365ac561726e5..fcfa7d923e7560 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -456,7 +456,7 @@ def __str__(self): self.assertEqual('{}'.format(EnumWithStrOverrides.one), 'Str!') def test_format_override_enum(self): - class EnumWithFormatOverride(float, Enum): + class EnumWithFormatOverride(Enum): one = 1.0 two = 2.0 def __format__(self, spec): diff --git a/Misc/ACKS b/Misc/ACKS index df0810fff46cc6..d2fba911fdf575 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -356,6 +356,7 @@ Tom Culliton Raúl Cumplido Antonio Cuni Brian Curtin +Jason Curtis Paul Dagnelie Lisandro Dalcin Darren Dale From 9ddf8ffb0e38702a8b6b75a1b2f2b500cff230a6 Mon Sep 17 00:00:00 2001 From: thatneat Date: Thu, 4 Jul 2019 08:30:51 -0700 Subject: [PATCH 6/7] fix markdown copy-paste --- Doc/library/enum.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 022bdd6ce282ee..ed3193dad07186 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -742,8 +742,8 @@ Some rules: 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 - :func:str and :func:repr methods. + Use the !s and !r format codes to force usage of the :class:`Enum` class's + :func:`str` and :func:`repr` methods. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------ From df99d40441bda96d977712fb54f5e4a0cb206d53 Mon Sep 17 00:00:00 2001 From: thatneat Date: Thu, 4 Jul 2019 10:57:14 -0700 Subject: [PATCH 7/7] dunder and func consistency in docs --- Doc/library/enum.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index ed3193dad07186..d7d319a9513450 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -743,7 +743,7 @@ Some rules: 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 - :func:`str` and :func:`repr` methods. + :meth:`__str__` and :meth:`__repr__` methods. When to use :meth:`__new__` vs. :meth:`__init__` ------------------------------------------------