Skip to content
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

bpo-42517: [Enum] do not convert private names into members #23722

Merged
merged 2 commits into from
Dec 10, 2020
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
9 changes: 9 additions & 0 deletions Doc/library/enum.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,15 @@ and raise an error if the two do not match::
In Python 2 code the :attr:`_order_` attribute is necessary as definition
order is lost before it can be recorded.


_Private__names
"""""""""""""""

Private names are not converted to Enum members, but remain normal attributes.

.. versionchanged:: 3.10


``Enum`` member type
""""""""""""""""""""

Expand Down
19 changes: 18 additions & 1 deletion Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ def _is_sunder(name):
name[-2:-1] != '_'
)

def _is_private(cls_name, name):
# do not use `re` as `re` imports `enum`
pattern = '_%s__' % (cls_name, )
if (
len(name) >= 5
and name.startswith(pattern)
and name[len(pattern)] != '_'
and (name[-1] != '_' or name[-2] != '_')
):
return True
else:
return False

def _make_class_unpicklable(cls):
"""
Make the given class un-picklable.
Expand Down Expand Up @@ -81,7 +94,10 @@ def __setitem__(self, key, value):

Single underscore (sunder) names are reserved.
"""
if _is_sunder(key):
if _is_private(self._cls_name, key):
# do nothing, name will be a normal attribute
pass
elif _is_sunder(key):
if key not in (
'_order_', '_create_pseudo_member_',
'_generate_next_value_', '_missing_', '_ignore_',
Expand Down Expand Up @@ -149,6 +165,7 @@ def __prepare__(metacls, cls, bases):
metacls._check_for_existing_members(cls, bases)
# create the namespace dict
enum_dict = _EnumDict()
enum_dict._cls_name = cls
# inherit previous flags and _generate_next_value_ function
member_type, first_enum = metacls._get_mixins_(cls, bases)
if first_enum is not None:
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ def test_multiple_mixin_mro(self):
class auto_enum(type(Enum)):
def __new__(metacls, cls, bases, classdict):
temp = type(classdict)()
temp._cls_name = cls
names = set(classdict._member_names)
i = 0
for k in classdict._member_names:
Expand Down Expand Up @@ -2118,6 +2119,30 @@ class ThirdFailedStrEnum(StrEnum):
one = '1'
two = b'2', 'ascii', 9

@unittest.skipUnless(
sys.version_info[:2] == (3, 9),
'private variables are now normal attributes',
)
def test_warning_for_private_variables(self):
with self.assertWarns(DeprecationWarning):
class Private(Enum):
__corporal = 'Radar'
self.assertEqual(Private._Private__corporal.value, 'Radar')
try:
with self.assertWarns(DeprecationWarning):
class Private(Enum):
__major_ = 'Hoolihan'
except ValueError:
pass

def test_private_variable_is_normal_attribute(self):
class Private(Enum):
__corporal = 'Radar'
__major_ = 'Hoolihan'
self.assertEqual(Private._Private__corporal, 'Radar')
self.assertEqual(Private._Private__major_, 'Hoolihan')


class TestOrder(unittest.TestCase):

def test_same_members(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Enum: private names do not become members / do not generate errors -- they
remain normal attributes