mirror of
https://github.com/python/cpython.git
synced 2025-08-04 17:08:35 +00:00
[3.11] gh-103479: [Enum] require __new__ to be considered a data type (GH-103495) (GH-103514)
a mixin must either have a __new__ method, or be a dataclass, to be interpreted as a data-type; an __init__ method is not enough (restores pre-3.11 behavior for non-dataclasses).
(cherry picked from commit a6f95941a3
)
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
parent
804a973d8e
commit
3b929a7b32
3 changed files with 12 additions and 9 deletions
|
@ -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__`
|
||||
|
|
|
@ -974,6 +974,7 @@ class EnumType(type):
|
|||
|
||||
@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:
|
||||
|
@ -986,7 +987,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__:
|
||||
if isinstance(base, EnumType):
|
||||
continue
|
||||
data_types.add(candidate or base)
|
||||
|
|
|
@ -2672,19 +2672,18 @@ class TestSpecial(unittest.TestCase):
|
|||
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):
|
||||
|
@ -2796,6 +2795,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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue