[3.11] gh-93910: [Enum] remove member.member deprecation (GH-103236) (GH-103299)

i.e. Color.RED.BLUE is now officially supported..
(cherry picked from commit 4ec8dd10bd)

Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
Ethan Furman 2023-04-05 21:29:14 -07:00 committed by GitHub
parent 0291397c57
commit 58e330ac9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 36 additions and 42 deletions

View file

@ -960,23 +960,11 @@ but remain normal attributes.
"""""""""""""""""""" """"""""""""""""""""
Enum members are instances of their enum class, and are normally accessed as 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 ``EnumClass.member``. In certain situations, such as writing custom enum
members from other members -- this practice was discouraged, and in ``3.11`` behavior, being able to access one member directly from another is useful,
:class:`Enum` returns to not allowing it:: and is supported.
>>> class FieldTypes(Enum):
... name = 0
... value = 1
... size = 2
...
>>> FieldTypes.value.size
Traceback (most recent call last):
...
AttributeError: <enum 'FieldTypes'> member has no attribute 'size'
.. versionchanged:: 3.5 .. versionchanged:: 3.5
.. versionchanged:: 3.11
Creating members that are mixed with other data types Creating members that are mixed with other data types

View file

@ -199,9 +199,13 @@ class property(DynamicClassAttribute):
) )
else: else:
if self.fget is None: if self.fget is None:
raise AttributeError( # look for a member by this name.
'%r member has no attribute %r' % (ownerclass, self.name) try:
) return ownerclass._member_map_[self.name]
except KeyError:
raise AttributeError(
'%r has no attribute %r' % (ownerclass, self.name)
) from None
else: else:
return self.fget(instance) return self.fget(instance)
@ -298,29 +302,29 @@ class _proto_member:
): ):
# no other instances found, record this member in _member_names_ # no other instances found, record this member in _member_names_
enum_class._member_names_.append(member_name) enum_class._member_names_.append(member_name)
# get redirect in place before adding to _member_map_ # if necessary, get redirect in place and then add it to _member_map_
# but check for other instances in parent classes first found_descriptor = None
need_override = False
descriptor = None
for base in enum_class.__mro__[1:]: for base in enum_class.__mro__[1:]:
descriptor = base.__dict__.get(member_name) descriptor = base.__dict__.get(member_name)
if descriptor is not None: if descriptor is not None:
if isinstance(descriptor, (property, DynamicClassAttribute)): if isinstance(descriptor, (property, DynamicClassAttribute)):
found_descriptor = descriptor
break break
else: elif (
need_override = True hasattr(descriptor, 'fget') and
# keep looking for an enum.property hasattr(descriptor, 'fset') and
if descriptor and not need_override: hasattr(descriptor, 'fdel')
# previous enum.property found, no further action needed ):
pass found_descriptor = descriptor
elif descriptor and need_override: continue
if found_descriptor:
redirect = property() redirect = property()
redirect.member = enum_member
redirect.__set_name__(enum_class, member_name) redirect.__set_name__(enum_class, member_name)
# Previous enum.property found, but some other inherited attribute # earlier descriptor found; copy fget, fset, fdel to this one.
# is in the way; copy fget, fset, fdel to this one. redirect.fget = found_descriptor.fget
redirect.fget = descriptor.fget redirect.fset = found_descriptor.fset
redirect.fset = descriptor.fset redirect.fdel = found_descriptor.fdel
redirect.fdel = descriptor.fdel
setattr(enum_class, member_name, redirect) setattr(enum_class, member_name, redirect)
else: else:
setattr(enum_class, member_name, enum_member) setattr(enum_class, member_name, enum_member)

View file

@ -2621,14 +2621,15 @@ class TestSpecial(unittest.TestCase):
self.assertEqual(Private._Private__corporal, 'Radar') self.assertEqual(Private._Private__corporal, 'Radar')
self.assertEqual(Private._Private__major_, 'Hoolihan') self.assertEqual(Private._Private__major_, 'Hoolihan')
@unittest.skip("Accessing all values retained for performance reasons, see GH-93910") def test_member_from_member_access(self):
def test_exception_for_member_from_member_access(self): class Di(Enum):
with self.assertRaisesRegex(AttributeError, "<enum .Di.> member has no attribute .NO."): YES = 1
class Di(Enum): NO = 0
YES = 1 name = 3
NO = 0 warn = Di.YES.NO
nope = 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): def test_dynamic_members_with_static_methods(self):
# #

View file

@ -0,0 +1 @@
Remove deprecation of enum ``memmber.member`` access.