mirror of https://github.com/python/cpython
gh-88123: Implement new Enum __contains__ (GH-93298)
Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
parent
6575841266
commit
9a479c3c10
26
Lib/enum.py
26
Lib/enum.py
|
@ -799,26 +799,16 @@ class EnumType(type):
|
|||
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
|
||||
|
|
|
@ -343,38 +343,12 @@ class _EnumTests:
|
|||
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):
|
||||
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
|
||||
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):
|
||||
MainEnum = self.MainEnum
|
||||
self.assertIn(MainEnum.first, MainEnum)
|
||||
self.assertTrue(self.source_values[0] in MainEnum)
|
||||
self.assertFalse('first' in 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)
|
||||
#
|
||||
|
@ -382,6 +356,43 @@ class _EnumTests:
|
|||
one = auto()
|
||||
two = auto()
|
||||
self.assertNotIn(OtherEnum.two, MainEnum)
|
||||
#
|
||||
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
|
||||
#
|
||||
class OtherEnum(Enum):
|
||||
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
|
||||
|
@ -1115,6 +1126,28 @@ class TestSpecial(unittest.TestCase):
|
|||
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'
|
||||
|
@ -2969,34 +3002,6 @@ class OldTestFlag(unittest.TestCase):
|
|||
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
|
||||
|
@ -3004,6 +3009,8 @@ class OldTestFlag(unittest.TestCase):
|
|||
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)
|
||||
|
||||
|
@ -3543,43 +3550,11 @@ class OldTestIntFlag(unittest.TestCase):
|
|||
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)
|
||||
|
@ -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`.
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Implement Enum __contains__ that returns True or False to replace the
|
||||
deprecated behaviour that would sometimes raise a TypeError.
|
Loading…
Reference in New Issue