Skip to content

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

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

Merged
merged 2 commits into from
Apr 6, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 3 additions & 15 deletions Doc/howto/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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: <enum 'FieldTypes'> 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
Expand Down
42 changes: 23 additions & 19 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
) from None
else:
return self.fget(instance)

Expand Down Expand Up @@ -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)
Expand Down
17 changes: 9 additions & 8 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, "<enum .Di.> 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):
#
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remove deprecation of enum ``memmber.member`` access.