Skip to content

Commit 650b70a

Browse files
carlbordumethanfurman
authored andcommitted
gh-88123: Implement new Enum __contains__
1 parent 6575841 commit 650b70a

File tree

3 files changed

+70
-110
lines changed

3 files changed

+70
-110
lines changed

Lib/enum.py

+10-17
Original file line numberDiff line numberDiff line change
@@ -800,25 +800,18 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, s
800800
)
801801

802802
def __contains__(cls, member):
803-
"""
804-
Return True if member is a member of this enum
805-
raises TypeError if member is not an enum member
803+
"""Return True if `member` is in `cls`.
804+
805+
`member` is in `cls` iff:
806+
1) `member` is a proper member of the `cls` enum, or
807+
2) `member` is the value of a member of the `cls` enum.
806808
807-
note: in 3.12 TypeError will no longer be raised, and True will also be
808-
returned if member is the value of a member in this enum
809+
Beware that 2) can lead to some confusion if members of different
810+
enums have the same value.
809811
"""
810-
if not isinstance(member, Enum):
811-
import warnings
812-
warnings.warn(
813-
"in 3.12 __contains__ will no longer raise TypeError, but will return True or\n"
814-
"False depending on whether the value is a member or the value of a member",
815-
DeprecationWarning,
816-
stacklevel=2,
817-
)
818-
raise TypeError(
819-
"unsupported operand type(s) for 'in': '%s' and '%s'" % (
820-
type(member).__qualname__, cls.__class__.__qualname__))
821-
return isinstance(member, cls) and member._name_ in cls._member_map_
812+
if isinstance(member, cls):
813+
return True
814+
return member in cls._value2member_map_ or member in cls._unhashable_values_
822815

823816
def __delattr__(cls, attr):
824817
# nicer error message when someone tries to delete an attribute

Lib/test/test_enum.py

+58-93
Original file line numberDiff line numberDiff line change
@@ -343,20 +343,15 @@ def test_changing_member_fails(self):
343343
with self.assertRaises(AttributeError):
344344
self.MainEnum.second = 'really first'
345345

346-
@unittest.skipIf(
347-
python_version >= (3, 12),
348-
'__contains__ now returns True/False for all inputs',
349-
)
350-
@unittest.expectedFailure
351-
def test_contains_er(self):
346+
def test_contains_tf(self):
352347
MainEnum = self.MainEnum
353-
self.assertIn(MainEnum.third, MainEnum)
354-
with self.assertRaises(TypeError):
355-
with self.assertWarns(DeprecationWarning):
356-
self.source_values[1] in MainEnum
357-
with self.assertRaises(TypeError):
358-
with self.assertWarns(DeprecationWarning):
359-
'first' in MainEnum
348+
self.assertIn(MainEnum.first, MainEnum)
349+
if type(self) is TestMinimalDate or type(self) is TestMixedDate:
350+
self.assertTrue(date(*self.source_values[0]) in MainEnum)
351+
else:
352+
self.assertTrue(self.source_values[0] in MainEnum)
353+
if type(self) is not TestStrEnum:
354+
self.assertFalse('first' in MainEnum)
360355
val = MainEnum.dupe
361356
self.assertIn(val, MainEnum)
362357
#
@@ -365,23 +360,26 @@ class OtherEnum(Enum):
365360
two = auto()
366361
self.assertNotIn(OtherEnum.two, MainEnum)
367362

368-
@unittest.skipIf(
369-
python_version < (3, 12),
370-
'__contains__ works only with enum memmbers before 3.12',
371-
)
372-
@unittest.expectedFailure
373-
def test_contains_tf(self):
363+
def test_contains_same_name_diff_enum_diff_values(self):
374364
MainEnum = self.MainEnum
375-
self.assertIn(MainEnum.first, MainEnum)
376-
self.assertTrue(self.source_values[0] in MainEnum)
377-
self.assertFalse('first' in MainEnum)
378-
val = MainEnum.dupe
379-
self.assertIn(val, MainEnum)
380-
#
381365
class OtherEnum(Enum):
382-
one = auto()
383-
two = auto()
384-
self.assertNotIn(OtherEnum.two, MainEnum)
366+
first = "brand"
367+
second = "new"
368+
third = "values"
369+
370+
self.assertIn(MainEnum.first, MainEnum)
371+
self.assertIn(MainEnum.second, MainEnum)
372+
self.assertIn(MainEnum.third, MainEnum)
373+
self.assertNotIn(MainEnum.first, OtherEnum)
374+
self.assertNotIn(MainEnum.second, OtherEnum)
375+
self.assertNotIn(MainEnum.third, OtherEnum)
376+
377+
self.assertIn(OtherEnum.first, OtherEnum)
378+
self.assertIn(OtherEnum.second, OtherEnum)
379+
self.assertIn(OtherEnum.third, OtherEnum)
380+
self.assertNotIn(OtherEnum.first, MainEnum)
381+
self.assertNotIn(OtherEnum.second, MainEnum)
382+
self.assertNotIn(OtherEnum.third, MainEnum)
385383

386384
def test_dir_on_class(self):
387385
TE = self.MainEnum
@@ -1115,6 +1113,28 @@ class Huh(Enum):
11151113
self.assertEqual(Huh.name.name, 'name')
11161114
self.assertEqual(Huh.name.value, 1)
11171115

1116+
def test_contains_name_and_value_overlap(self):
1117+
class IntEnum1(IntEnum):
1118+
X = 1
1119+
class IntEnum2(IntEnum):
1120+
X = 1
1121+
class IntEnum3(IntEnum):
1122+
X = 2
1123+
class IntEnum4(IntEnum):
1124+
Y = 1
1125+
self.assertIn(IntEnum1.X, IntEnum1)
1126+
self.assertIn(IntEnum1.X, IntEnum2)
1127+
self.assertNotIn(IntEnum1.X, IntEnum3)
1128+
self.assertIn(IntEnum1.X, IntEnum4)
1129+
1130+
def test_contains_different_types_same_members(self):
1131+
class IntEnum1(IntEnum):
1132+
X = 1
1133+
class IntFlag1(IntFlag):
1134+
X = 1
1135+
self.assertIn(IntEnum1.X, IntFlag1)
1136+
self.assertIn(IntFlag1.X, IntEnum1)
1137+
11181138
def test_inherited_data_type(self):
11191139
class HexInt(int):
11201140
__qualname__ = 'HexInt'
@@ -2969,41 +2989,15 @@ def test_pickle(self):
29692989
test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE)
29702990
test_pickle_dump_load(self.assertIs, FlagStooges)
29712991

2972-
@unittest.skipIf(
2973-
python_version >= (3, 12),
2974-
'__contains__ now returns True/False for all inputs',
2975-
)
2976-
@unittest.expectedFailure
2977-
def test_contains_er(self):
2978-
Open = self.Open
2979-
Color = self.Color
2980-
self.assertFalse(Color.BLACK in Open)
2981-
self.assertFalse(Open.RO in Color)
2982-
with self.assertRaises(TypeError):
2983-
with self.assertWarns(DeprecationWarning):
2984-
'BLACK' in Color
2985-
with self.assertRaises(TypeError):
2986-
with self.assertWarns(DeprecationWarning):
2987-
'RO' in Open
2988-
with self.assertRaises(TypeError):
2989-
with self.assertWarns(DeprecationWarning):
2990-
1 in Color
2991-
with self.assertRaises(TypeError):
2992-
with self.assertWarns(DeprecationWarning):
2993-
1 in Open
2994-
2995-
@unittest.skipIf(
2996-
python_version < (3, 12),
2997-
'__contains__ only works with enum memmbers before 3.12',
2998-
)
2999-
@unittest.expectedFailure
30002992
def test_contains_tf(self):
30012993
Open = self.Open
30022994
Color = self.Color
30032995
self.assertFalse(Color.BLACK in Open)
30042996
self.assertFalse(Open.RO in Color)
30052997
self.assertFalse('BLACK' in Color)
30062998
self.assertFalse('RO' in Open)
2999+
self.assertTrue(Color.BLACK in Color)
3000+
self.assertTrue(Open.RO in Open)
30073001
self.assertTrue(1 in Color)
30083002
self.assertTrue(1 in Open)
30093003

@@ -3543,43 +3537,11 @@ def test_programatic_function_from_empty_tuple(self):
35433537
self.assertEqual(len(lst), len(Thing))
35443538
self.assertEqual(len(Thing), 0, Thing)
35453539

3546-
@unittest.skipIf(
3547-
python_version >= (3, 12),
3548-
'__contains__ now returns True/False for all inputs',
3549-
)
3550-
@unittest.expectedFailure
3551-
def test_contains_er(self):
3552-
Open = self.Open
3553-
Color = self.Color
3554-
self.assertTrue(Color.GREEN in Color)
3555-
self.assertTrue(Open.RW in Open)
3556-
self.assertFalse(Color.GREEN in Open)
3557-
self.assertFalse(Open.RW in Color)
3558-
with self.assertRaises(TypeError):
3559-
with self.assertWarns(DeprecationWarning):
3560-
'GREEN' in Color
3561-
with self.assertRaises(TypeError):
3562-
with self.assertWarns(DeprecationWarning):
3563-
'RW' in Open
3564-
with self.assertRaises(TypeError):
3565-
with self.assertWarns(DeprecationWarning):
3566-
2 in Color
3567-
with self.assertRaises(TypeError):
3568-
with self.assertWarns(DeprecationWarning):
3569-
2 in Open
3570-
3571-
@unittest.skipIf(
3572-
python_version < (3, 12),
3573-
'__contains__ only works with enum memmbers before 3.12',
3574-
)
3575-
@unittest.expectedFailure
35763540
def test_contains_tf(self):
35773541
Open = self.Open
35783542
Color = self.Color
35793543
self.assertTrue(Color.GREEN in Color)
35803544
self.assertTrue(Open.RW in Open)
3581-
self.assertTrue(Color.GREEN in Open)
3582-
self.assertTrue(Open.RW in Color)
35833545
self.assertFalse('GREEN' in Color)
35843546
self.assertFalse('RW' in Open)
35853547
self.assertTrue(2 in Color)
@@ -4088,11 +4050,14 @@ class Color(enum.Enum)
40884050
| Methods inherited from enum.EnumType:
40894051
|\x20\x20
40904052
| __contains__(member) from enum.EnumType
4091-
| Return True if member is a member of this enum
4092-
| raises TypeError if member is not an enum member
4093-
|\x20\x20\x20\x20\x20\x20
4094-
| note: in 3.12 TypeError will no longer be raised, and True will also be
4095-
| returned if member is the value of a member in this enum
4053+
| Return True if `member` is in `cls`.
4054+
|
4055+
| `member` is in `cls` iff:
4056+
| 1) `member` is a proper member of the `cls` enum, or
4057+
| 2) `member` is the value of a member of the `cls` enum.
4058+
|
4059+
| Beware that 2) can lead to some confusion if members of different
4060+
| enums have the same value.
40964061
|\x20\x20
40974062
| __getitem__(name) from enum.EnumType
40984063
| Return the member matching `name`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement Enum __contains__ that returns True or False to replace the
2+
deprecated behaviour that would sometimes raise a TypeError.

0 commit comments

Comments
 (0)