From: Ethan Furman Date: Thu, 13 Apr 2023 19:04:06 +0000 (-0700) Subject: [3.11] gh-103479: [Enum] require __new__ to be considered a data type (GH-103495... X-Git-Tag: v3.11.4~198 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3b929a7b321dae113593d81caf47c4f08890c615;p=thirdparty%2FPython%2Fcpython.git [3.11] gh-103479: [Enum] require __new__ to be considered a data type (GH-103495) (GH-103514) a mixin must either have a __new__ method, or be a dataclass, to be interpreted as a data-type; an __init__ method is not enough (restores pre-3.11 behavior for non-dataclasses). (cherry picked from commit a6f95941a3d686707fb38e0f37758e666f25e180) Co-authored-by: Ethan Furman --- diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 32310692fe56..55f0dfb4c48c 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -837,17 +837,18 @@ Some rules: 4. When another data type is mixed in, the :attr:`value` attribute is *not the same* as the enum member itself, although it is equivalent and will compare equal. -5. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's +5. A ``data type`` is a mixin that defines :meth:`__new__`. +6. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's :meth:`__str__` and :meth:`__repr__` respectively; other codes (such as ``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type. -6. :ref:`Formatted string literals `, :meth:`str.format`, +7. :ref:`Formatted string literals `, :meth:`str.format`, and :func:`format` will use the enum's :meth:`__str__` method. .. note:: Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are designed to be drop-in replacements for existing constants, their - :meth:`__str__` method has been reset to their data types + :meth:`__str__` method has been reset to their data types' :meth:`__str__` method. When to use :meth:`__new__` vs. :meth:`__init__` diff --git a/Lib/enum.py b/Lib/enum.py index a884b50541e5..26e5c50bf856 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -974,6 +974,7 @@ class EnumType(type): @classmethod def _find_data_type_(mcls, class_name, bases): + # a datatype has a __new__ method data_types = set() base_chain = set() for chain in bases: @@ -986,7 +987,7 @@ class EnumType(type): if base._member_type_ is not object: data_types.add(base._member_type_) break - elif '__new__' in base.__dict__ or '__init__' in base.__dict__: + elif '__new__' in base.__dict__ or '__dataclass_fields__' in base.__dict__: if isinstance(base, EnumType): continue data_types.add(candidate or base) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index d3b4832e3406..188e1a174756 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2672,19 +2672,18 @@ class TestSpecial(unittest.TestCase): self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value) self.assertEqual(repr(Entries.ENTRY1), '') - def test_repr_with_init_data_type_mixin(self): - # non-data_type is a mixin that doesn't define __new__ + def test_repr_with_init_mixin(self): class Foo: def __init__(self, a): self.a = a def __repr__(self): - return f'Foo(a={self.a!r})' + return 'Foo(a=%r)' % self._value_ class Entries(Foo, Enum): ENTRY1 = 1 # - self.assertEqual(repr(Entries.ENTRY1), '') + self.assertEqual(repr(Entries.ENTRY1), 'Foo(a=1)') - def test_repr_and_str_with_non_data_type_mixin(self): + def test_repr_and_str_with_no_init_mixin(self): # non-data_type is a mixin that doesn't define __new__ class Foo: def __repr__(self): @@ -2796,6 +2795,8 @@ class TestSpecial(unittest.TestCase): def test_init_exception(self): class Base: + def __new__(cls, *args): + return object.__new__(cls) def __init__(self, x): raise ValueError("I don't like", x) with self.assertRaises(TypeError):