Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.11] gh-103479: [Enum] require __new__ to be considered a data type (GH-103495) #103514

Merged
merged 4 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Doc/howto/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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__`
Expand Down
3 changes: 2 additions & 1 deletion Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,6 +974,7 @@ def _find_data_repr_(mcls, class_name, bases):

@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:
Expand All @@ -986,7 +987,7 @@ def _find_data_type_(mcls, class_name, bases):
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)
Expand Down
11 changes: 6 additions & 5 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -2672,19 +2672,18 @@ class Entries(Foo, Enum):
self.assertTrue(Entries.ENTRY1.value == Foo(1), Entries.ENTRY1.value)
self.assertEqual(repr(Entries.ENTRY1), '<Entries.ENTRY1: Foo(a=1)>')

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), '<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):
Expand Down Expand Up @@ -2796,6 +2795,8 @@ def __new__(cls, c):

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):
Expand Down