From ab60fdda7fbc6d341c70de66c9724a1ddf8c9b9e Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Thu, 13 Apr 2023 08:31:03 -0700 Subject: [PATCH 1/3] [3.11] 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. (cherry picked from commit a6f95941a3d686707fb38e0f37758e666f25e180) Co-authored-by: Ethan Furman --- Doc/howto/enum.rst | 8 +++++--- Lib/enum.py | 3 ++- Lib/test/test_enum.py | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index 32310692fe56ed..dbd8d4fa9231ee 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -837,17 +837,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 `, :meth:`str.format`, +7. :ref:`Formatted string literals `, :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 84ae339d0d4020..0ef6559b3c6bd6 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -981,6 +981,7 @@ def _find_data_repr_(mcls, class_name, bases): @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: @@ -993,7 +994,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__: if isinstance(base, EnumType): continue data_types.add(candidate or base) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 51dd66bea0c42c..2d615edee580d2 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2667,13 +2667,14 @@ class Foo: a: int class Entries(Foo, Enum): ENTRY1 = 1 + self.assertEqual(repr(Entries.ENTRY1), '') + 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), '') - 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 @@ -2682,9 +2683,9 @@ def __repr__(self): class Entries(Foo, Enum): ENTRY1 = 1 # - self.assertEqual(repr(Entries.ENTRY1), '') + 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): @@ -2790,6 +2791,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): From 102b7f8ac6defe7c16d6f438922ced74cf3a7bd1 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Thu, 13 Apr 2023 09:58:19 -0700 Subject: [PATCH 2/3] remove mention of dataclass --- Doc/howto/enum.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index dbd8d4fa9231ee..55f0dfb4c48c38 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -837,8 +837,7 @@ 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. A ``data type`` is a mixin that defines :meth:`__new__`, or a - :class:`~dataclasses.dataclass` +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. From faff581517e19529986b44577203fe55f6f9be10 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Thu, 13 Apr 2023 10:58:11 -0700 Subject: [PATCH 3/3] restore mention of dataclass --- Lib/enum.py | 4 ++-- Lib/test/test_enum.py | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 0ef6559b3c6bd6..5b175820972f4c 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -981,7 +981,7 @@ def _find_data_repr_(mcls, class_name, bases): @classmethod def _find_data_type_(mcls, class_name, bases): - # a datatype has a __new__ method, or a __dataclass_fields__ attribute + # a datatype has a __new__ method data_types = set() base_chain = set() for chain in bases: @@ -994,7 +994,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__: + elif '__new__' in base.__dict__ or '__dataclass_fields__' in base.__dict__: if isinstance(base, EnumType): continue data_types.add(candidate or base) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 2d615edee580d2..397bb849d00aba 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2667,8 +2667,6 @@ class Foo: a: int class Entries(Foo, Enum): ENTRY1 = 1 - self.assertEqual(repr(Entries.ENTRY1), '') - 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) @@ -2679,7 +2677,7 @@ 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 # pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy