From f396a1a940f8608a4be2a9ac4ef82e37c198ecd3 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Mon, 12 Apr 2021 12:16:46 -0700 Subject: [PATCH] [3.8] bpo-42248: [Enum] ensure exceptions raised in ``_missing_`` are released (GH-25350). (GH-25369) (cherry picked from commit 8c14f5a787b21d5a1eae5d5ee981431d1c0e055f) Co-authored-by: Ethan Furman --- Lib/enum.py | 31 ++++++++++-------- Lib/test/test_enum.py | 32 +++++++++++++++++++ .../2021-04-11-21-10-57.bpo-42248.pedB1E.rst | 1 + 3 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-04-11-21-10-57.bpo-42248.pedB1E.rst diff --git a/Lib/enum.py b/Lib/enum.py index 31afdd3a24f7..3d0b1797aebe 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -654,19 +654,24 @@ class Enum(metaclass=EnumMeta): except Exception as e: exc = e result = None - if isinstance(result, cls): - return result - else: - ve_exc = ValueError("%r is not a valid %s" % (value, cls.__name__)) - if result is None and exc is None: - raise ve_exc - elif exc is None: - exc = TypeError( - 'error in %s._missing_: returned %r instead of None or a valid member' - % (cls.__name__, result) - ) - exc.__context__ = ve_exc - raise exc + try: + if isinstance(result, cls): + return result + else: + ve_exc = ValueError("%r is not a valid %s" % (value, cls.__name__)) + if result is None and exc is None: + raise ve_exc + elif exc is None: + exc = TypeError( + 'error in %s._missing_: returned %r instead of None or a valid member' + % (cls.__name__, result) + ) + exc.__context__ = ve_exc + raise exc + finally: + # ensure all variables that could hold an exception are destroyed + exc = None + ve_exc = None def _generate_next_value_(name, start, count, last_values): """ diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index b5e4009bb068..b246163fff4e 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1897,6 +1897,38 @@ class TestEnum(unittest.TestCase): else: raise Exception('Exception not raised.') + def test_missing_exceptions_reset(self): + import weakref + # + class TestEnum(enum.Enum): + VAL1 = 'val1' + VAL2 = 'val2' + # + class Class1: + def __init__(self): + # Gracefully handle an exception of our own making + try: + raise ValueError() + except ValueError: + pass + # + class Class2: + def __init__(self): + # Gracefully handle an exception of Enum's making + try: + TestEnum('invalid_value') + except ValueError: + pass + # No strong refs here so these are free to die. + class_1_ref = weakref.ref(Class1()) + class_2_ref = weakref.ref(Class2()) + # + # The exception raised by Enum creates a reference loop and thus + # Class2 instances will stick around until the next gargage collection + # cycle, unlike Class1. + self.assertIs(class_1_ref(), None) + self.assertIs(class_2_ref(), None) + def test_multiple_mixin(self): class MaxMixin: @classproperty diff --git a/Misc/NEWS.d/next/Library/2021-04-11-21-10-57.bpo-42248.pedB1E.rst b/Misc/NEWS.d/next/Library/2021-04-11-21-10-57.bpo-42248.pedB1E.rst new file mode 100644 index 000000000000..0722d35a37a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-04-11-21-10-57.bpo-42248.pedB1E.rst @@ -0,0 +1 @@ +[Enum] ensure exceptions raised in ``_missing__`` are released -- 2.47.3