summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan Furman <ethan@stoneleaf.us>2023-04-13 15:31:03 (GMT)
committerGitHub <noreply@github.com>2023-04-13 15:31:03 (GMT)
commita6f95941a3d686707fb38e0f37758e666f25e180 (patch)
tree0ea29dae5df68a1d67ec01e648659c79f9ff15e6
parent2194071540313e2bbdc7214d77453b9ce3034a5c (diff)
downloadcpython-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.rst8
-rw-r--r--Lib/enum.py3
-rw-r--r--Lib/test/test_enum.py13
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):