8000 gh-88123: Implement new Enum __contains__ by carlbordum · Pull Request #93298 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

gh-88123: Implement new Enum __contains__ #93298

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

Merged
merged 2 commits into from
Jun 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Ju 8000 mp to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
gh-88123: Implement new Enum __contains__
  • Loading branch information
carlbordum authored and ethanfurman committed Jun 22, 2022
commit 650b70ab5076fb38334e0eb6dac6d91bd910a5a7
27 changes: 10 additions & 17 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -800,25 +800,18 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, s
)

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

`member` is in `cls` iff:
1) `member` is a proper member of the `cls` enum, or
2) `member` is the value of a member of the `cls` enum.

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
Beware that 2) can lead to some confusion if members of different
enums have the same value.
"""
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(member, cls):
return True
return member in cls._value2member_map_ or member in cls._unhashable_values_

def __delattr__(cls, attr):
# nicer error message when someone tries to delete an attribute
Expand Down
151 changes: 58 additions & 93 deletions Lib/test/test_enum.py
8000
Original file line number Diff line number Diff line change
Expand Up @@ -343,20 +343,15 @@ 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)
if type(self) is TestMinimalDate or type(self) is TestMixedDate:
self.assertTrue(date(*self.source_values[0]) in MainEnum)
else:
self.assertTrue(self.source_values[0] in MainEnum)
if type(self) is not TestStrEnum:
self.assertFalse('first' in MainEnum)
val = MainEnum.dupe
self.assertIn(val, MainEnum)
#
Expand All @@ -365,23 +360,26 @@ class OtherEnum(Enum):
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):
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 +1113,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 +2989,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 +3537,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 @@ -4088,11 +4050,14 @@ 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
| Return True if `member` is in `cls`.
|
| `member` is in `cls` iff:
| 1) `member` is a proper member of the `cls` enum, or
| 2) `member` is the value of a member of the `cls` enum.
|
| Beware that 2) can lead to some confusion if members of different
| enums have the same value.
|\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.
0