Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-88123: Implement new Enum __contains__ #93298

Merged
merged 2 commits into from
Jun 22, 2022
Merged
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
26 changes: 8 additions & 18 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,26 +799,16 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, s
boundary=boundary,
)

def __contains__(cls, member):
"""
Return True if member is a member of this enum
raises TypeError if member is not an enum member
def __contains__(cls, value):
"""Return True if `value` is in `cls`.

note: in 3.12 TypeError will no longer be raised, and True will also be
returned if member is the value of a member in this enum
`value` is in `cls` if:
1) `value` is a member of `cls`, or
2) `value` is the value of one of the `cls`'s members.
"""
if not isinstance(member, Enum):
import warnings
warnings.warn(
"in 3.12 __contains__ will no longer raise TypeError, but will return True or\n"
"False depending on whether the value is a member or the value of a member",
DeprecationWarning,
stacklevel=2,
)
raise TypeError(
"unsupported operand type(s) for 'in': '%s' and '%s'" % (
type(member).__qualname__, cls.__class__.__qualname__))
return isinstance(member, cls) and member._name_ in cls._member_map_
if isinstance(value, cls):
return True
return value in cls._value2member_map_ or value in cls._unhashable_values_

def __delattr__(cls, attr):
# nicer error message when someone tries to delete an attribute
Expand Down
163 changes: 69 additions & 94 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,45 +343,56 @@ def test_changing_member_fails(self):
with self.assertRaises(AttributeError):
self.MainEnum.second = 'really first'

@unittest.skipIf(
python_version >= (3, 12),
'__contains__ now returns True/False for all inputs',
)
@unittest.expectedFailure
def test_contains_er(self):
def test_contains_tf(self):
MainEnum = self.MainEnum
self.assertIn(MainEnum.third, MainEnum)
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
self.source_values[1] in MainEnum
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
'first' in MainEnum
self.assertIn(MainEnum.first, MainEnum)
self.assertTrue(self.values[0] in MainEnum)
if type(self) is not TestStrEnum:
self.assertFalse('first' in MainEnum)
val = MainEnum.dupe
self.assertIn(val, MainEnum)
#
class OtherEnum(Enum):
one = auto()
two = auto()
self.assertNotIn(OtherEnum.two, MainEnum)

@unittest.skipIf(
python_version < (3, 12),
'__contains__ works only with enum memmbers before 3.12',
)
@unittest.expectedFailure
def test_contains_tf(self):
#
if MainEnum._member_type_ is object:
# enums without mixed data types will always be False
class NotEqualEnum(self.enum_type):
this = self.source_values[0]
that = self.source_values[1]
self.assertNotIn(NotEqualEnum.this, MainEnum)
self.assertNotIn(NotEqualEnum.that, MainEnum)
else:
# enums with mixed data types may be True
class EqualEnum(self.enum_type):
this = self.source_values[0]
that = self.source_values[1]
self.assertIn(EqualEnum.this, MainEnum)
self.assertIn(EqualEnum.that, MainEnum)

def test_contains_same_name_diff_enum_diff_values(self):
MainEnum = self.MainEnum
self.assertIn(MainEnum.first, MainEnum)
self.assertTrue(self.source_values[0] in MainEnum)
self.assertFalse('first' in MainEnum)
val = MainEnum.dupe
self.assertIn(val, MainEnum)
#
class OtherEnum(Enum):
one = auto()
two = auto()
self.assertNotIn(OtherEnum.two, MainEnum)
first = "brand"
second = "new"
third = "values"
#
self.assertIn(MainEnum.first, MainEnum)
self.assertIn(MainEnum.second, MainEnum)
self.assertIn(MainEnum.third, MainEnum)
self.assertNotIn(MainEnum.first, OtherEnum)
self.assertNotIn(MainEnum.second, OtherEnum)
self.assertNotIn(MainEnum.third, OtherEnum)
#
self.assertIn(OtherEnum.first, OtherEnum)
self.assertIn(OtherEnum.second, OtherEnum)
self.assertIn(OtherEnum.third, OtherEnum)
self.assertNotIn(OtherEnum.first, MainEnum)
self.assertNotIn(OtherEnum.second, MainEnum)
self.assertNotIn(OtherEnum.third, MainEnum)

def test_dir_on_class(self):
TE = self.MainEnum
Expand Down Expand Up @@ -1115,6 +1126,28 @@ class Huh(Enum):
self.assertEqual(Huh.name.name, 'name')
self.assertEqual(Huh.name.value, 1)

def test_contains_name_and_value_overlap(self):
class IntEnum1(IntEnum):
X = 1
class IntEnum2(IntEnum):
X = 1
class IntEnum3(IntEnum):
X = 2
class IntEnum4(IntEnum):
Y = 1
self.assertIn(IntEnum1.X, IntEnum1)
self.assertIn(IntEnum1.X, IntEnum2)
self.assertNotIn(IntEnum1.X, IntEnum3)
self.assertIn(IntEnum1.X, IntEnum4)

def test_contains_different_types_same_members(self):
class IntEnum1(IntEnum):
X = 1
class IntFlag1(IntFlag):
X = 1
self.assertIn(IntEnum1.X, IntFlag1)
self.assertIn(IntFlag1.X, IntEnum1)

def test_inherited_data_type(self):
class HexInt(int):
__qualname__ = 'HexInt'
Expand Down Expand Up @@ -2969,41 +3002,15 @@ def test_pickle(self):
test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE)
test_pickle_dump_load(self.assertIs, FlagStooges)

@unittest.skipIf(
python_version >= (3, 12),
'__contains__ now returns True/False for all inputs',
)
@unittest.expectedFailure
def test_contains_er(self):
Open = self.Open
Color = self.Color
self.assertFalse(Color.BLACK in Open)
self.assertFalse(Open.RO in Color)
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
'BLACK' in Color
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
'RO' in Open
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
1 in Color
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
1 in Open

@unittest.skipIf(
python_version < (3, 12),
'__contains__ only works with enum memmbers before 3.12',
)
@unittest.expectedFailure
def test_contains_tf(self):
Open = self.Open
Color = self.Color
self.assertFalse(Color.BLACK in Open)
self.assertFalse(Open.RO in Color)
self.assertFalse('BLACK' in Color)
self.assertFalse('RO' in Open)
self.assertTrue(Color.BLACK in Color)
self.assertTrue(Open.RO in Open)
self.assertTrue(1 in Color)
self.assertTrue(1 in Open)

Expand Down Expand Up @@ -3543,43 +3550,11 @@ def test_programatic_function_from_empty_tuple(self):
self.assertEqual(len(lst), len(Thing))
self.assertEqual(len(Thing), 0, Thing)

@unittest.skipIf(
python_version >= (3, 12),
'__contains__ now returns True/False for all inputs',
)
@unittest.expectedFailure
def test_contains_er(self):
Open = self.Open
Color = self.Color
self.assertTrue(Color.GREEN in Color)
self.assertTrue(Open.RW in Open)
self.assertFalse(Color.GREEN in Open)
self.assertFalse(Open.RW in Color)
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
'GREEN' in Color
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
'RW' in Open
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
2 in Color
with self.assertRaises(TypeError):
with self.assertWarns(DeprecationWarning):
2 in Open

@unittest.skipIf(
python_version < (3, 12),
'__contains__ only works with enum memmbers before 3.12',
)
@unittest.expectedFailure
def test_contains_tf(self):
Open = self.Open
Color = self.Color
self.assertTrue(Color.GREEN in Color)
self.assertTrue(Open.RW in Open)
self.assertTrue(Color.GREEN in Open)
self.assertTrue(Open.RW in Color)
self.assertFalse('GREEN' in Color)
self.assertFalse('RW' in Open)
self.assertTrue(2 in Color)
Expand Down Expand Up @@ -4087,12 +4062,12 @@ class Color(enum.Enum)
| ----------------------------------------------------------------------
| Methods inherited from enum.EnumType:
|\x20\x20
| __contains__(member) from enum.EnumType
| Return True if member is a member of this enum
| raises TypeError if member is not an enum member
|\x20\x20\x20\x20\x20\x20
| note: in 3.12 TypeError will no longer be raised, and True will also be
| returned if member is the value of a member in this enum
| __contains__(value) from enum.EnumType
| Return True if `value` is in `cls`.
|
| `value` is in `cls` if:
| 1) `value` is a member of `cls`, or
| 2) `value` is the value of one of the `cls`'s members.
|\x20\x20
| __getitem__(name) from enum.EnumType
| Return the member matching `name`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Implement Enum __contains__ that returns True or False to replace the
deprecated behaviour that would sometimes raise a TypeError.