From f71ed32e72d499b2966769721792f8bf6e7e6609 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Mon, 8 Feb 2021 16:27:13 -0800 Subject: [PATCH] [Enum] deprecate enum member.member access In 3.5 (?) a speed optimization made it possible to access members as attributes of other members, i.e. ``Color.RED.BLUE``. This was always discouraged in the docs, and other recent optimizations has made that one no longer necessary. Because some may be relying on it anyway, it is being deprecated in 3.10, and will be removed in 3.11. --- Lib/enum.py | 18 +++++++++++---- Lib/test/test_enum.py | 23 +++++++++++++++++++ .../2021-02-08-16-27-00.bpo-43162.t-W7h3.rst | 2 ++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-02-08-16-27-00.bpo-43162.t-W7h3.rst diff --git a/Lib/enum.py b/Lib/enum.py index d4b11521ab27f3..55299c57882446 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -139,12 +139,22 @@ def __get__(self, instance, ownerclass=None): return ownerclass._member_map_[self.name] except KeyError: raise AttributeError( - '%s: no attribute %r' % (ownerclass.__name__, self.name) + '%s: no class attribute %r' % (ownerclass.__name__, self.name) ) else: if self.fget is None: + # check for member + if self.name in ownerclass._member_map_: + import warnings + warnings.warn( + "accessing one member from another is not supported, " + " and will be disabled in 3.11", + DeprecationWarning, + stacklevel=2, + ) + return ownerclass._member_map_[self.name] raise AttributeError( - '%s: no attribute %r' % (ownerclass.__name__, self.name) + '%s: no instance attribute %r' % (ownerclass.__name__, self.name) ) else: return self.fget(instance) @@ -152,7 +162,7 @@ def __get__(self, instance, ownerclass=None): def __set__(self, instance, value): if self.fset is None: raise AttributeError( - "%s: cannot set attribute %r" % (self.clsname, self.name) + "%s: cannot set instance attribute %r" % (self.clsname, self.name) ) else: return self.fset(instance, value) @@ -160,7 +170,7 @@ def __set__(self, instance, value): def __delete__(self, instance): if self.fdel is None: raise AttributeError( - "%s: cannot delete attribute %r" % (self.clsname, self.name) + "%s: cannot delete instance attribute %r" % (self.clsname, self.name) ) else: return self.fdel(instance) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 96de878faf72d4..3982d1d6430430 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2185,6 +2185,29 @@ class Private(Enum): self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__major_, 'Hoolihan') + @unittest.skipUnless( + sys.version_info[:2] == (3, 10), + 'member-member access now raises an exception', + ) + def test_warning_for_member_from_member_access(self): + with self.assertWarns(DeprecationWarning): + class Di(Enum): + YES = 1 + NO = 0 + nope = Di.YES.NO + self.assertIs(Di.NO, nope) + + @unittest.skipUnless( + sys.version_info[:2] > (3, 10), + 'member-member access currently issues a warning', + ) + def test_exception_for_member_from_member_access(self): + with self.assertRaisesRegex(AttributeError, "Di: no instance attribute .NO."): + class Di(Enum): + YES = 1 + NO = 0 + nope = Di.YES.NO + def test_strenum_auto(self): class Strings(StrEnum): ONE = auto() diff --git a/Misc/NEWS.d/next/Library/2021-02-08-16-27-00.bpo-43162.t-W7h3.rst b/Misc/NEWS.d/next/Library/2021-02-08-16-27-00.bpo-43162.t-W7h3.rst new file mode 100644 index 00000000000000..fef5915e762ee0 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-02-08-16-27-00.bpo-43162.t-W7h3.rst @@ -0,0 +1,2 @@ +deprecate unsupported ability to access enum members as attributes of other +enum members