Skip to content

Commit a6f9594

Browse files
authored
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
1 parent 2194071 commit a6f9594

File tree

3 files changed

+14
-10
lines changed

3 files changed

+14
-10
lines changed

Doc/howto/enum.rst

+5-3
Original file line numberDiff line numberDiff line change
@@ -865,17 +865,19 @@ Some rules:
865865
4. When another data type is mixed in, the :attr:`value` attribute is *not the
866866
same* as the enum member itself, although it is equivalent and will compare
867867
equal.
868-
5. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's
868+
5. A ``data type`` is a mixin that defines :meth:`__new__`, or a
869+
:class:`~dataclasses.dataclass`
870+
6. %-style formatting: ``%s`` and ``%r`` call the :class:`Enum` class's
869871
:meth:`__str__` and :meth:`__repr__` respectively; other codes (such as
870872
``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type.
871-
6. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
873+
7. :ref:`Formatted string literals <f-strings>`, :meth:`str.format`,
872874
and :func:`format` will use the enum's :meth:`__str__` method.
873875

874876
.. note::
875877

876878
Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are
877879
designed to be drop-in replacements for existing constants, their
878-
:meth:`__str__` method has been reset to their data types
880+
:meth:`__str__` method has been reset to their data types'
879881
:meth:`__str__` method.
880882

881883
When to use :meth:`__new__` vs. :meth:`__init__`

Lib/enum.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,7 @@ def _find_data_repr_(mcls, class_name, bases):
967967

968968
@classmethod
969969
def _find_data_type_(mcls, class_name, bases):
970+
# a datatype has a __new__ method, or a __dataclass_fields__ attribute
970971
data_types = set()
971972
base_chain = set()
972973
for chain in bases:
@@ -979,7 +980,7 @@ def _find_data_type_(mcls, class_name, bases):
979980
if base._member_type_ is not object:
980981
data_types.add(base._member_type_)
981982
break
982-
elif '__new__' in base.__dict__ or '__init__' in base.__dict__:
983+
elif '__new__' in base.__dict__ or '__dataclass_fields__' in base.__dict__:
983984
data_types.add(candidate or base)
984985
break
985986
else:

Lib/test/test_enum.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -2737,10 +2737,10 @@ def __repr__(self):
27372737
return 'ha hah!'
27382738
class Entries(Foo, Enum):
27392739
ENTRY1 = 1
2740+
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
2741+
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
27402742
self.assertTrue(isinstance(Entries.ENTRY1, Foo))
27412743
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
2742-
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
2743-
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: ha hah!>')
27442744
#
27452745
# check auto-generated dataclass __repr__ is not used
27462746
#
@@ -2787,8 +2787,7 @@ class Creature(CreatureDataMixin, Enum):
27872787
DOG = ('medium', 4)
27882788
self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
27892789

2790-
def test_repr_with_init_data_type_mixin(self):
2791-
# non-data_type is a mixin that doesn't define __new__
2790+
def test_repr_with_init_mixin(self):
27922791
class Foo:
27932792
def __init__(self, a):
27942793
self.a = a
@@ -2797,9 +2796,9 @@ def __repr__(self):
27972796
class Entries(Foo, Enum):
27982797
ENTRY1 = 1
27992798
#
2800-
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')
2799+
self.assertEqual(repr(Entries.ENTRY1), 'Foo(a=1)')
28012800

2802-
def test_repr_and_str_with_non_data_type_mixin(self):
2801+
def test_repr_and_str_with_no_init_mixin(self):
28032802
# non-data_type is a mixin that doesn't define __new__
28042803
class Foo:
28052804
def __repr__(self):
@@ -2911,6 +2910,8 @@ def __new__(cls, c):
29112910

29122911
def test_init_exception(self):
29132912
class Base:
2913+
def __new__(cls, *args):
2914+
return object.__new__(cls)
29142915
def __init__(self, x):
29152916
raise ValueError("I don't like", x)
29162917
with self.assertRaises(TypeError):

0 commit comments

Comments
 (0)