Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,9 +624,13 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
'__invert__'
):
if name not in classdict:
# check for mixin overrides before replacing
enum_method = getattr(Flag, name)
setattr(enum_class, name, enum_method)
classdict[name] = enum_method
found_method = getattr(enum_class, name)
data_type_method = getattr(member_type, name, None)
if found_method in (enum_method, data_type_method):
setattr(enum_class, name, enum_method)
classdict[name] = enum_method
#
# replace any other __new__ with our own (as long as Enum is not None,
# anyway) -- again, this is to support pickle
Expand Down
53 changes: 53 additions & 0 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -4080,6 +4080,39 @@ class NeverEnum(WhereEnum):
self.assertFalse(NeverEnum.__dict__.get('_test1', False))
self.assertFalse(NeverEnum.__dict__.get('_test2', False))

def test_mixin_operator_override(self):
# a mixin's own bitwise-operator overrides must not be clobbered
# by Flag's default __or__/__and__/__xor__/__invert__ -- gh-121291
class OperatorMixin:
def __or__(self, other):
return 'mixin-or'
def __ror__(self, other):
return 'mixin-ror'
def __invert__(self):
return 'mixin-invert'
class MixedFlag(OperatorMixin, Flag):
A = 1
B = 2
self.assertIs(MixedFlag.__or__, OperatorMixin.__or__)
self.assertIs(MixedFlag.__ror__, OperatorMixin.__ror__)
self.assertIs(MixedFlag.__invert__, OperatorMixin.__invert__)
self.assertEqual(MixedFlag.A | MixedFlag.B, 'mixin-or')
self.assertEqual(1 | MixedFlag.A, 'mixin-ror')
self.assertEqual(~MixedFlag.A, 'mixin-invert')
# dunders the mixin didn't override still get Flag's own
self.assertIs(MixedFlag.__and__, Flag.__and__)
self.assertIs(MixedFlag.__xor__, Flag.__xor__)
self.assertIs(MixedFlag.__rand__, Flag.__rand__)
self.assertIs(MixedFlag.__rxor__, Flag.__rxor__)
self.assertEqual(MixedFlag.A & MixedFlag.B, MixedFlag(0))
#
# a plain (non-mixin) Flag subclass is unaffected
class PlainFlag(Flag):
A = 1
B = 2
self.assertIs(PlainFlag.__or__, Flag.__or__)
self.assertEqual(PlainFlag.A | PlainFlag.B, PlainFlag(3))


class OldTestIntFlag(unittest.TestCase):
"""Tests of the IntFlags."""
Expand Down Expand Up @@ -4564,6 +4597,26 @@ def cycle_enum():
'at least one thread failed while creating composite members')
self.assertEqual(256, len(seen), 'too many composite members created')

def test_mixin_operator_override(self):
# IntFlag's own mixed-in `int` also defines these operators, so the
# fix for gh-121291 must still override `int`'s raw operators with
# Flag's (returning IntFlag instances, not plain ints), while still
# respecting a genuine, separate mixin's override.
Color = self.Color
combined = Color.RED | Color.BLUE
self.assertIs(type(combined), Color)
self.assertEqual(combined, Color.PURPLE)
self.assertEqual(repr(combined), '<Color.PURPLE: 5>')
#
class OperatorMixin:
def __or__(self, other):
return 'mixin-or'
class MixedIntFlag(OperatorMixin, IntFlag):
A = 1
B = 2
self.assertIs(MixedIntFlag.__or__, OperatorMixin.__or__)
self.assertEqual(MixedIntFlag.A | MixedIntFlag.B, 'mixin-or')


class TestEmptyAndNonLatinStrings(unittest.TestCase):

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:class:`enum.Flag` (and :class:`enum.IntFlag`) subclasses no longer have
a mixin base's own ``__or__``, ``__and__``, ``__xor__``, ``__ror__``,
``__rand__``, ``__rxor__``, or ``__invert__`` override silently replaced
by :class:`~enum.Flag`'s default implementation.
Loading