Skip to content

Commit

Permalink
gh-93910: [Enum] remove member.member deprecation (GH-103236)
Browse files Browse the repository at this point in the history
i.e. Color.RED.BLUE is now officially supported.
  • Loading branch information
ethanfurman authored Apr 6, 2023
1 parent b4978ff commit 4ec8dd1
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 50 deletions.
7 changes: 3 additions & 4 deletions Doc/howto/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -988,12 +988,11 @@ but remain normal attributes.
""""""""""""""""""""

Enum members are instances of their enum class, and are normally accessed as
``EnumClass.member``. In Python versions starting with ``3.5`` you could access
members from other members -- this practice is discouraged, is deprecated
in ``3.12``, and will be removed in ``3.14``.
``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.12


Creating members that are mixed with other data types
Expand Down
50 changes: 25 additions & 25 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,23 +201,13 @@ def __get__(self, instance, ownerclass=None):
)
else:
if self.fget is None:
if self.member is None: # not sure this can happen, but just in case
# 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)
)
# issue warning deprecating this behavior
import warnings
warnings.warn(
"`member.member` access (e.g. `Color.RED.BLUE`) is "
"deprecated and will be removed in 3.14.",
DeprecationWarning,
stacklevel=2,
)
return self.member
# XXX: uncomment in 3.14 and remove warning above
# raise AttributeError(
# '%r member has no attribute %r' % (ownerclass, self.name)
# )
else:
return self.fget(instance)

Expand Down Expand Up @@ -314,22 +304,32 @@ 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
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
redirect = property()
redirect.member = enum_member
redirect.__set_name__(enum_class, member_name)
if descriptor:
redirect.fget = getattr(descriptor, 'fget', None)
redirect.fset = getattr(descriptor, 'fset', None)
redirect.fdel = getattr(descriptor, 'fdel', None)
setattr(enum_class, member_name, redirect)
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)
# 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)
# now add to _member_map_ (even aliases)
enum_class._member_map_[member_name] = enum_member
try:
Expand Down
29 changes: 8 additions & 21 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -2686,28 +2686,15 @@ class Private(Enum):
self.assertEqual(Private._Private__corporal, 'Radar')
self.assertEqual(Private._Private__major_, 'Hoolihan')

@unittest.skipIf(
python_version <= (3, 13),
'member.member access currently deprecated',
)
def test_exception_for_member_from_member_access(self):
with self.assertRaisesRegex(AttributeError, "<enum .Di.> member has no attribute .NO."):
class Di(Enum):
YES = 1
NO = 0
nope = Di.YES.NO

@unittest.skipIf(
python_version > (3, 13),
'member.member access now raises',
)
def test_warning_for_member_from_member_access(self):
with self.assertWarnsRegex(DeprecationWarning, '`member.member` access .* is deprecated and will be removed in 3.14'):
class Di(Enum):
YES = 1
NO = 0
warn = 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):
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove deprecation of enum ``memmber.member`` access.

0 comments on commit 4ec8dd1

Please sign in to comment.