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

[3.7] bpo-33217: deprecate non-Enum lookups in Enums #6392

Merged
merged 4 commits into from Apr 12, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Doc/library/enum.rst
Expand Up @@ -976,7 +976,7 @@ Enum Classes
The :class:`EnumMeta` metaclass is responsible for providing the
:meth:`__contains__`, :meth:`__dir__`, :meth:`__iter__` and other methods that
allow one to do things with an :class:`Enum` class that fail on a typical
class, such as `list(Color)` or `some_var in Color`. :class:`EnumMeta` is
class, such as `list(Color)` or `some_enum_var in Color`. :class:`EnumMeta` is
responsible for ensuring that various other methods on the final :class:`Enum`
class are correct (such as :meth:`__new__`, :meth:`__getnewargs__`,
:meth:`__str__` and :meth:`__repr__`).
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.7.rst
Expand Up @@ -1041,6 +1041,12 @@ Deprecated
:meth:`ssl.SSLContext.wrap_socket` instead.
(Contributed by Christian Heimes in :issue:`28124`.)

- In Python 3.8, attempting to check for non-Enum objects in :class:`Enum`
classes will raise a :exc:`TypeError` (e.g. ``1 in Color``); similarly,
attempting to check for non-Flag objects in a :class:`Flag` member will
raise :exc:`TypeError` (e.g. ``1 in Perm.RW``); currently, both operations
return :const:`False` instead.


Windows Only
------------
Expand Down
13 changes: 12 additions & 1 deletion Lib/enum.py
Expand Up @@ -309,6 +309,12 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, s
return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start)

def __contains__(cls, member):
if not isinstance(member, Enum):
import warnings
warnings.warn(
"using non-Enums in containment checks will raise "
"TypeError in Python 3.8",
DeprecationWarning, 2)
return isinstance(member, cls) and member._name_ in cls._member_map_

def __delattr__(cls, attr):
Expand Down Expand Up @@ -713,7 +719,12 @@ def _create_pseudo_member_(cls, value):

def __contains__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
import warnings
warnings.warn(
"using non-Flags in containment checks will raise "
"TypeError in Python 3.8",
DeprecationWarning, 2)
return False
return other._value_ & self._value_ == other._value_

def __repr__(self):
Expand Down
60 changes: 57 additions & 3 deletions Lib/test/test_enum.py
Expand Up @@ -325,7 +325,10 @@ class IntLogic(int, Enum):
def test_contains(self):
Season = self.Season
self.assertIn(Season.AUTUMN, Season)
self.assertNotIn(3, Season)
with self.assertWarns(DeprecationWarning):
self.assertNotIn(3, Season)
with self.assertWarns(DeprecationWarning):
self.assertNotIn('AUTUMN', Season)

val = Season(3)
self.assertIn(val, Season)
Expand All @@ -334,6 +337,11 @@ class OtherEnum(Enum):
one = 1; two = 2
self.assertNotIn(OtherEnum.two, Season)

def test_member_contains(self):
self.assertRaises(TypeError, lambda: 'test' in self.Season.AUTUMN)
self.assertRaises(TypeError, lambda: 3 in self.Season.AUTUMN)
self.assertRaises(TypeError, lambda: 'AUTUMN' in self.Season.AUTUMN)

def test_comparisons(self):
Season = self.Season
with self.assertRaises(TypeError):
Expand Down Expand Up @@ -1745,6 +1753,13 @@ class TestFlag(unittest.TestCase):
class Perm(Flag):
R, W, X = 4, 2, 1

class Color(Flag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
PURPLE = RED|BLUE

class Open(Flag):
RO = 0
WO = 1
Expand Down Expand Up @@ -1954,7 +1969,21 @@ def test_pickle(self):
test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE)
test_pickle_dump_load(self.assertIs, FlagStooges)

def test_containment(self):
def test_contains(self):
Open = self.Open
Color = self.Color
self.assertFalse(Color.BLACK in Open)
self.assertFalse(Open.RO in Color)
with self.assertWarns(DeprecationWarning):
self.assertFalse('BLACK' in Color)
with self.assertWarns(DeprecationWarning):
self.assertFalse('RO' in Open)
with self.assertWarns(DeprecationWarning):
self.assertFalse(1 in Color)
with self.assertWarns(DeprecationWarning):
self.assertFalse(1 in Open)

def test_member_contains(self):
Perm = self.Perm
R, W, X = Perm
RW = R | W
Expand Down Expand Up @@ -2065,6 +2094,13 @@ class Perm(IntFlag):
W = 1 << 1
R = 1 << 2

class Color(IntFlag):
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
PURPLE = RED|BLUE

class Open(IntFlag):
RO = 0
WO = 1
Expand Down Expand Up @@ -2340,7 +2376,23 @@ def test_programatic_function_from_empty_tuple(self):
self.assertEqual(len(lst), len(Thing))
self.assertEqual(len(Thing), 0, Thing)

def test_containment(self):
def test_contains(self):
Color = self.Color
Open = self.Open
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.assertWarns(DeprecationWarning):
self.assertFalse('GREEN' in Color)
with self.assertWarns(DeprecationWarning):
self.assertFalse('RW' in Open)
with self.assertWarns(DeprecationWarning):
self.assertFalse(2 in Color)
with self.assertWarns(DeprecationWarning):
self.assertFalse(2 in Open)

def test_member_contains(self):
Perm = self.Perm
R, W, X = Perm
RW = R | W
Expand All @@ -2359,6 +2411,8 @@ def test_containment(self):
self.assertFalse(R in WX)
self.assertFalse(W in RX)
self.assertFalse(X in RW)
with self.assertWarns(DeprecationWarning):
self.assertFalse('swallow' in RW)

def test_bool(self):
Perm = self.Perm
Expand Down
@@ -0,0 +1,2 @@
Deprecate looking up non-Enum objects in Enum classes and Enum members (will
raise :exc:`TypeError` in 3.8+).