From 6d1c4e4841adf7668029aca72aa0772cffa349fb Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Wed, 16 Sep 2020 07:11:57 -0700 Subject: [PATCH] bpo-41517: do not allow Enums to be extended (GH-22271) fix bug that let Enums be extended via multiple inheritance (cherry picked from commit 3064dbf5df1021e85b507366a7ea448c8895efe7) Co-authored-by: Ethan Furman --- Lib/enum.py | 19 ++++++++++++++----- Lib/test/test_enum.py | 3 +++ .../2020-09-15-22-43-30.bpo-41517.sLBH7g.rst | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-09-15-22-43-30.bpo-41517.sLBH7g.rst diff --git a/Lib/enum.py b/Lib/enum.py index c892d738f8b385..4c1acc6e360bd7 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -123,10 +123,12 @@ class EnumMeta(type): """Metaclass for Enum""" @classmethod def __prepare__(metacls, cls, bases): + # check that previous enum members do not exist + metacls._check_for_existing_members(cls, bases) # create the namespace dict enum_dict = _EnumDict() # inherit previous flags and _generate_next_value_ function - member_type, first_enum = metacls._get_mixins_(bases) + member_type, first_enum = metacls._get_mixins_(cls, bases) if first_enum is not None: enum_dict['_generate_next_value_'] = getattr(first_enum, '_generate_next_value_', None) return enum_dict @@ -142,7 +144,7 @@ def __new__(metacls, cls, bases, classdict): ignore = classdict['_ignore_'] for key in ignore: classdict.pop(key, None) - member_type, first_enum = metacls._get_mixins_(bases) + member_type, first_enum = metacls._get_mixins_(cls, bases) __new__, save_new, use_args = metacls._find_new_(classdict, member_type, first_enum) @@ -401,7 +403,7 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s """ metacls = cls.__class__ bases = (cls, ) if type is None else (type, cls) - _, first_enum = cls._get_mixins_(bases) + _, first_enum = cls._get_mixins_(cls, bases) classdict = metacls.__prepare__(class_name, bases) # special processing needed for names? @@ -480,7 +482,14 @@ def _convert(cls, *args, **kwargs): return cls._convert_(*args, **kwargs) @staticmethod - def _get_mixins_(bases): + def _check_for_existing_members(class_name, bases): + for chain in bases: + for base in chain.__mro__: + if issubclass(base, Enum) and base._member_names_: + raise TypeError("%s: cannot extend enumeration %r" % (class_name, base.__name__)) + + @staticmethod + def _get_mixins_(class_name, bases): """Returns the type for creating enum members, and the first inherited enum class. @@ -505,7 +514,7 @@ def _find_data_type(bases): elif not issubclass(base, Enum): candidate = base if len(data_types) > 1: - raise TypeError('too many data types: %r' % data_types) + raise TypeError('%r: too many data types: %r' % (class_name, data_types)) elif data_types: return data_types[0] else: diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 32ff32cf175676..11e88dba6b5b36 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1000,6 +1000,9 @@ class MoreColor(Color): cyan = 4 magenta = 5 yellow = 6 + with self.assertRaisesRegex(TypeError, "EvenMoreColor: cannot extend enumeration 'Color'"): + class EvenMoreColor(Color, IntEnum): + chartruese = 7 def test_exclude_methods(self): class whatever(Enum): diff --git a/Misc/NEWS.d/next/Library/2020-09-15-22-43-30.bpo-41517.sLBH7g.rst b/Misc/NEWS.d/next/Library/2020-09-15-22-43-30.bpo-41517.sLBH7g.rst new file mode 100644 index 00000000000000..e7654711062cef --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-09-15-22-43-30.bpo-41517.sLBH7g.rst @@ -0,0 +1 @@ +fix bug allowing Enums to be extended via multiple inheritance