gh-121291: Respect mixin bitwise-operator overrides on Flag subclasses (GH-155862)
* gh-121291: respect mixin-defined bitwise operators on Flag subclasses
EnumMeta.__new__ unconditionally installed Flag's __or__/__and__/__xor__/
__ror__/__rand__/__rxor__/__invert__ onto every Flag subclass, silently
discarding a mixin base's own override of these dunders -- even though
the class's MRO should have resolved to the mixin's method; this fixes that.
diff --git a/Lib/enum.py b/Lib/enum.py
index 7aff36c..076aa18 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -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
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index b05eab4..447f847 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -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."""
@@ -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):
diff --git a/Misc/NEWS.d/next/Library/2026-08-15-18-16-16.gh-issue-121291.WljPkh.rst b/Misc/NEWS.d/next/Library/2026-08-15-18-16-16.gh-issue-121291.WljPkh.rst
new file mode 100644
index 0000000..e6d9f4b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-15-18-16-16.gh-issue-121291.WljPkh.rst
@@ -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.