]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-103479: [Enum] require __new__ to be considered a data type (GH-103495)
authorEthan Furman <ethan@stoneleaf.us>
Thu, 13 Apr 2023 15:31:03 +0000 (08:31 -0700)
committerGitHub <noreply@github.com>
Thu, 13 Apr 2023 15:31:03 +0000 (08:31 -0700)
a mixin must either have a __new__ method, or be a dataclass, to be interpreted as a data-type

Doc/howto/enum.rst
Lib/enum.py
Lib/test/test_enum.py

index 9390faded2fb8d56ecfaf424b77648d6891854ed..56391a026cf889dbddaa14c52ffadb6be7afe7a5 100644 (file)
@@ -865,17 +865,19 @@ 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__`, or a
+   :class:`~dataclasses.dataclass`
+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 <f-strings>`, :meth:`str.format`,
+7. :ref:`Formatted string literals <f-strings>`, :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__`
index 432d7456b4b9f1ede69b749bf3d67545c8f7cd52..e9f224a303d3e59b81783add81fb76f00ba880e6 100644 (file)
@@ -967,6 +967,7 @@ class EnumType(type):
 
     @classmethod
     def _find_data_type_(mcls, class_name, bases):
+        # a datatype has a __new__ method, or a __dataclass_fields__ attribute
         data_types = set()
         base_chain = set()
         for chain in bases:
@@ -979,7 +980,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__:
                     data_types.add(candidate or base)
                     break
                 else:
index 89294e95df2a83e04f38d4fdd305c3eda612616a..e9dfcf8586a823415d59c844b3c16d72362615ba 100644 (file)
@@ -2737,10 +2737,10 @@ class TestSpecial(unittest.TestCase):
                 return 'ha hah!'
         class Entries(Foo, Enum):
             ENTRY1 = 1
+        self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
+        self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
         self.assertTrue(isinstance(Entries.ENTRY1, Foo))
         self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
-        self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
-        self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
         #
         # check auto-generated dataclass __repr__ is not used
         #
@@ -2787,8 +2787,7 @@ class TestSpecial(unittest.TestCase):
             DOG = ('medium', 4)
         self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
 
-    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
@@ -2797,9 +2796,9 @@ class TestSpecial(unittest.TestCase):
         class Entries(Foo, Enum):
             ENTRY1 = 1
         #
-        self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
+        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):
@@ -2911,6 +2910,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):