diff options
author | Ethan Furman <ethan@stoneleaf.us> | 2023-04-13 15:31:03 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-13 15:31:03 (GMT) |
commit | a6f95941a3d686707fb38e0f37758e666f25e180 (patch) | |
tree | 0ea29dae5df68a1d67ec01e648659c79f9ff15e6 | |
parent | 2194071540313e2bbdc7214d77453b9ce3034a5c (diff) | |
download | cpython-a6f95941a3d686707fb38e0f37758e666f25e180.zip cpython-a6f95941a3d686707fb38e0f37758e666f25e180.tar.gz cpython-a6f95941a3d686707fb38e0f37758e666f25e180.tar.bz2 |
gh-103479: [Enum] require __new__ to be considered a data type (GH-103495)
a mixin must either have a __new__ method, or be a dataclass, to be interpreted as a data-type
-rw-r--r-- | Doc/howto/enum.rst | 8 | ||||
-rw-r--r-- | Lib/enum.py | 3 | ||||
-rw-r--r-- | Lib/test/test_enum.py | 13 |
3 files changed, 14 insertions, 10 deletions
diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 9390fad..56391a0 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -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__` diff --git a/Lib/enum.py b/Lib/enum.py index 432d745..e9f224a 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -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: diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 89294e9..e9dfcf8 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -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): |