bpo-42517: [Enum] do not convert private names into members (GH-23722)

private names, such as `_Color__hue` and `_Color__hue_` are now normal attributes, and do not become members nor raise exceptions
This commit is contained in:
Ethan Furman 2020-12-09 17:12:11 -08:00 committed by GitHub
parent 6bd94de168
commit 7cf0aad96d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 54 additions and 1 deletions

View File

@ -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 In Python 2 code the :attr:`_order_` attribute is necessary as definition
order is lost before it can be recorded. 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 ``Enum`` member type
"""""""""""""""""""" """"""""""""""""""""

View File

@ -49,6 +49,19 @@ def _is_sunder(name):
name[-2:-1] != '_' 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): def _make_class_unpicklable(cls):
""" """
Make the given class un-picklable. Make the given class un-picklable.
@ -89,7 +102,10 @@ class _EnumDict(dict):
Single underscore (sunder) names are reserved. 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 ( if key not in (
'_order_', '_create_pseudo_member_', '_order_', '_create_pseudo_member_',
'_generate_next_value_', '_missing_', '_ignore_', '_generate_next_value_', '_missing_', '_ignore_',
@ -157,6 +173,7 @@ class EnumMeta(type):
metacls._check_for_existing_members(cls, bases) metacls._check_for_existing_members(cls, bases)
# create the namespace dict # create the namespace dict
enum_dict = _EnumDict() enum_dict = _EnumDict()
enum_dict._cls_name = cls
# inherit previous flags and _generate_next_value_ function # inherit previous flags and _generate_next_value_ function
member_type, first_enum = metacls._get_mixins_(cls, bases) member_type, first_enum = metacls._get_mixins_(cls, bases)
if first_enum is not None: if first_enum is not None:

View File

@ -1149,6 +1149,7 @@ class TestEnum(unittest.TestCase):
class auto_enum(type(Enum)): class auto_enum(type(Enum)):
def __new__(metacls, cls, bases, classdict): def __new__(metacls, cls, bases, classdict):
temp = type(classdict)() temp = type(classdict)()
temp._cls_name = cls
names = set(classdict._member_names) names = set(classdict._member_names)
i = 0 i = 0
for k in classdict._member_names: for k in classdict._member_names:
@ -2155,6 +2156,30 @@ class TestEnum(unittest.TestCase):
self.assertFalse(NeverEnum.__dict__.get('_test2', False)) self.assertFalse(NeverEnum.__dict__.get('_test2', False))
@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): class TestOrder(unittest.TestCase):
def test_same_members(self): def test_same_members(self):

View File

@ -0,0 +1,2 @@
Enum: private names do not become members / do not generate errors -- they
remain normal attributes