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
This commit is contained in:
parent
2194071540
commit
a6f95941a3
@ -865,17 +865,19 @@ Some rules:
|
|||||||
4. When another data type is mixed in, the :attr:`value` attribute is *not the
|
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
|
same* as the enum member itself, although it is equivalent and will compare
|
||||||
equal.
|
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
|
:meth:`__str__` and :meth:`__repr__` respectively; other codes (such as
|
||||||
``%i`` or ``%h`` for IntEnum) treat the enum member as its mixed-in type.
|
``%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.
|
and :func:`format` will use the enum's :meth:`__str__` method.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are
|
Because :class:`IntEnum`, :class:`IntFlag`, and :class:`StrEnum` are
|
||||||
designed to be drop-in replacements for existing constants, their
|
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.
|
:meth:`__str__` method.
|
||||||
|
|
||||||
When to use :meth:`__new__` vs. :meth:`__init__`
|
When to use :meth:`__new__` vs. :meth:`__init__`
|
||||||
|
@ -967,6 +967,7 @@ class EnumType(type):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _find_data_type_(mcls, class_name, bases):
|
def _find_data_type_(mcls, class_name, bases):
|
||||||
|
# a datatype has a __new__ method, or a __dataclass_fields__ attribute
|
||||||
data_types = set()
|
data_types = set()
|
||||||
base_chain = set()
|
base_chain = set()
|
||||||
for chain in bases:
|
for chain in bases:
|
||||||
@ -979,7 +980,7 @@ class EnumType(type):
|
|||||||
if base._member_type_ is not object:
|
if base._member_type_ is not object:
|
||||||
data_types.add(base._member_type_)
|
data_types.add(base._member_type_)
|
||||||
break
|
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)
|
data_types.add(candidate or base)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -2737,10 +2737,10 @@ class TestSpecial(unittest.TestCase):
|
|||||||
return 'ha hah!'
|
return 'ha hah!'
|
||||||
class Entries(Foo, Enum):
|
class Entries(Foo, Enum):
|
||||||
ENTRY1 = 1
|
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(isinstance(Entries.ENTRY1, Foo))
|
||||||
self.assertTrue(Entries._member_type_ is Foo, Entries._member_type_)
|
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
|
# check auto-generated dataclass __repr__ is not used
|
||||||
#
|
#
|
||||||
@ -2787,8 +2787,7 @@ class TestSpecial(unittest.TestCase):
|
|||||||
DOG = ('medium', 4)
|
DOG = ('medium', 4)
|
||||||
self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
|
self.assertRegex(repr(Creature.DOG), "<Creature.DOG: .*CreatureDataMixin object at .*>")
|
||||||
|
|
||||||
def test_repr_with_init_data_type_mixin(self):
|
def test_repr_with_init_mixin(self):
|
||||||
# non-data_type is a mixin that doesn't define __new__
|
|
||||||
class Foo:
|
class Foo:
|
||||||
def __init__(self, a):
|
def __init__(self, a):
|
||||||
self.a = a
|
self.a = a
|
||||||
@ -2797,9 +2796,9 @@ class TestSpecial(unittest.TestCase):
|
|||||||
class Entries(Foo, Enum):
|
class Entries(Foo, Enum):
|
||||||
ENTRY1 = 1
|
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__
|
# non-data_type is a mixin that doesn't define __new__
|
||||||
class Foo:
|
class Foo:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@ -2911,6 +2910,8 @@ class TestSpecial(unittest.TestCase):
|
|||||||
|
|
||||||
def test_init_exception(self):
|
def test_init_exception(self):
|
||||||
class Base:
|
class Base:
|
||||||
|
def __new__(cls, *args):
|
||||||
|
return object.__new__(cls)
|
||||||
def __init__(self, x):
|
def __init__(self, x):
|
||||||
raise ValueError("I don't like", x)
|
raise ValueError("I don't like", x)
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user