From e57316550e7bb03404069875e9fb235c29360626 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Wed, 5 Apr 2023 17:33:52 -0700 Subject: [PATCH 1/2] [3.11] gh-93910: [Enum] remove member.member deprecation (GH-103236) i.e. Color.RED.BLUE is now officially supported.. (cherry picked from commit 4ec8dd10bd4682793559c4eccbcf6ae00688c4c3) Co-authored-by: Ethan Furman --- Doc/howto/enum.rst | 18 ++------ Lib/enum.py | 42 ++++++++++--------- Lib/test/test_enum.py | 17 ++++---- ...3-04-04-12-43-38.gh-issue-93910.jurMzv.rst | 1 + 4 files changed, 36 insertions(+), 42 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-04-04-12-43-38.gh-issue-93910.jurMzv.rst diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst index f2a10c03fa88df..32310692fe56ed 100644 --- a/Doc/howto/enum.rst +++ b/Doc/howto/enum.rst @@ -960,23 +960,11 @@ but remain normal attributes. """""""""""""""""""" Enum members are instances of their enum class, and are normally accessed as -``EnumClass.member``. In Python versions ``3.5`` to ``3.10`` you could access -members from other members -- this practice was discouraged, and in ``3.11`` -:class:`Enum` returns to not allowing it:: - - >>> class FieldTypes(Enum): - ... name = 0 - ... value = 1 - ... size = 2 - ... - >>> FieldTypes.value.size - Traceback (most recent call last): - ... - AttributeError: member has no attribute 'size' - +``EnumClass.member``. In certain situations, such as writing custom enum +behavior, being able to access one member directly from another is useful, +and is supported. .. versionchanged:: 3.5 -.. versionchanged:: 3.11 Creating members that are mixed with other data types diff --git a/Lib/enum.py b/Lib/enum.py index 13da287b34aa5a..8df30ca0279a64 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -199,9 +199,13 @@ def __get__(self, instance, ownerclass=None): ) else: if self.fget is None: - raise AttributeError( - '%r member has no attribute %r' % (ownerclass, self.name) - ) + # look for a member by this name. + try: + return ownerclass._member_map_[self.name] + except KeyError: + raise AttributeError( + '%r has no attribute %r' % (ownerclass, self.name) + ) else: return self.fget(instance) @@ -298,29 +302,29 @@ def __set_name__(self, enum_class, member_name): ): # no other instances found, record this member in _member_names_ enum_class._member_names_.append(member_name) - # get redirect in place before adding to _member_map_ - # but check for other instances in parent classes first - need_override = False - descriptor = None + # if necessary, get redirect in place and then add it to _member_map_ + found_descriptor = None for base in enum_class.__mro__[1:]: descriptor = base.__dict__.get(member_name) if descriptor is not None: if isinstance(descriptor, (property, DynamicClassAttribute)): + found_descriptor = descriptor break - else: - need_override = True - # keep looking for an enum.property - if descriptor and not need_override: - # previous enum.property found, no further action needed - pass - elif descriptor and need_override: + elif ( + hasattr(descriptor, 'fget') and + hasattr(descriptor, 'fset') and + hasattr(descriptor, 'fdel') + ): + found_descriptor = descriptor + continue + if found_descriptor: redirect = property() + redirect.member = enum_member redirect.__set_name__(enum_class, member_name) - # Previous enum.property found, but some other inherited attribute - # is in the way; copy fget, fset, fdel to this one. - redirect.fget = descriptor.fget - redirect.fset = descriptor.fset - redirect.fdel = descriptor.fdel + # earlier descriptor found; copy fget, fset, fdel to this one. + redirect.fget = found_descriptor.fget + redirect.fset = found_descriptor.fset + redirect.fdel = found_descriptor.fdel setattr(enum_class, member_name, redirect) else: setattr(enum_class, member_name, enum_member) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 09060733bc5139..51dd66bea0c42c 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2621,14 +2621,15 @@ class Private(Enum): self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__major_, 'Hoolihan') - @unittest.skip("Accessing all values retained for performance reasons, see GH-93910") - def test_exception_for_member_from_member_access(self): - with self.assertRaisesRegex(AttributeError, " member has no attribute .NO."): - class Di(Enum): - YES = 1 - NO = 0 - nope = Di.YES.NO - + def test_member_from_member_access(self): + class Di(Enum): + YES = 1 + NO = 0 + name = 3 + warn = Di.YES.NO + self.assertIs(warn, Di.NO) + self.assertIs(Di.name, Di['name']) + self.assertEqual(Di.name.name, 'name') def test_dynamic_members_with_static_methods(self): # diff --git a/Misc/NEWS.d/next/Library/2023-04-04-12-43-38.gh-issue-93910.jurMzv.rst b/Misc/NEWS.d/next/Library/2023-04-04-12-43-38.gh-issue-93910.jurMzv.rst new file mode 100644 index 00000000000000..783aefae0770a9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-04-04-12-43-38.gh-issue-93910.jurMzv.rst @@ -0,0 +1 @@ +Remove deprecation of enum ``memmber.member`` access. From 02a0f1c9a54212eead1770bb81ee3ac872b3ab63 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Wed, 5 Apr 2023 20:26:48 -0700 Subject: [PATCH 2/2] Update Lib/enum.py --- Lib/enum.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/enum.py b/Lib/enum.py index 8df30ca0279a64..84ae339d0d4020 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -205,7 +205,7 @@ def __get__(self, instance, ownerclass=None): except KeyError: raise AttributeError( '%r has no attribute %r' % (ownerclass, self.name) - ) + ) from None else: return self.fget(instance)