Skip to content

Commit 7cf0aad

Browse files
authored
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
1 parent 6bd94de commit 7cf0aad

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

Doc/library/enum.rst

+9
Original file line numberDiff line numberDiff line change
@@ -1164,6 +1164,15 @@ and raise an error if the two do not match::
11641164
In Python 2 code the :attr:`_order_` attribute is necessary as definition
11651165
order is lost before it can be recorded.
11661166

1167+
1168+
_Private__names
1169+
"""""""""""""""
1170+
1171+
Private names are not converted to Enum members, but remain normal attributes.
1172+
1173+
.. versionchanged:: 3.10
1174+
1175+
11671176
``Enum`` member type
11681177
""""""""""""""""""""
11691178

Lib/enum.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ def _is_sunder(name):
4949
name[-2:-1] != '_'
5050
)
5151

52+
def _is_private(cls_name, name):
53+
# do not use `re` as `re` imports `enum`
54+
pattern = '_%s__' % (cls_name, )
55+
if (
56+
len(name) >= 5
57+
and name.startswith(pattern)
58+
and name[len(pattern)] != '_'
59+
and (name[-1] != '_' or name[-2] != '_')
60+
):
61+
return True
62+
else:
63+
return False
64+
5265
def _make_class_unpicklable(cls):
5366
"""
5467
Make the given class un-picklable.
@@ -89,7 +102,10 @@ def __setitem__(self, key, value):
89102
90103
Single underscore (sunder) names are reserved.
91104
"""
92-
if _is_sunder(key):
105+
if _is_private(self._cls_name, key):
106+
# do nothing, name will be a normal attribute
107+
pass
108+
elif _is_sunder(key):
93109
if key not in (
94110
'_order_', '_create_pseudo_member_',
95111
'_generate_next_value_', '_missing_', '_ignore_',
@@ -157,6 +173,7 @@ def __prepare__(metacls, cls, bases):
157173
metacls._check_for_existing_members(cls, bases)
158174
# create the namespace dict
159175
enum_dict = _EnumDict()
176+
enum_dict._cls_name = cls
160177
# inherit previous flags and _generate_next_value_ function
161178
member_type, first_enum = metacls._get_mixins_(cls, bases)
162179
if first_enum is not None:

Lib/test/test_enum.py

+25
Original file line numberDiff line numberDiff line change
@@ -1149,6 +1149,7 @@ def test_multiple_mixin_mro(self):
11491149
class auto_enum(type(Enum)):
11501150
def __new__(metacls, cls, bases, classdict):
11511151
temp = type(classdict)()
1152+
temp._cls_name = cls
11521153
names = set(classdict._member_names)
11531154
i = 0
11541155
for k in classdict._member_names:
@@ -2155,6 +2156,30 @@ class NeverEnum(WhereEnum):
21552156
self.assertFalse(NeverEnum.__dict__.get('_test2', False))
21562157

21572158

2159+
@unittest.skipUnless(
2160+
sys.version_info[:2] == (3, 9),
2161+
'private variables are now normal attributes',
2162+
)
2163+
def test_warning_for_private_variables(self):
2164+
with self.assertWarns(DeprecationWarning):
2165+
class Private(Enum):
2166+
__corporal = 'Radar'
2167+
self.assertEqual(Private._Private__corporal.value, 'Radar')
2168+
try:
2169+
with self.assertWarns(DeprecationWarning):
2170+
class Private(Enum):
2171+
__major_ = 'Hoolihan'
2172+
except ValueError:
2173+
pass
2174+
2175+
def test_private_variable_is_normal_attribute(self):
2176+
class Private(Enum):
2177+
__corporal = 'Radar'
2178+
__major_ = 'Hoolihan'
2179+
self.assertEqual(Private._Private__corporal, 'Radar')
2180+
self.assertEqual(Private._Private__major_, 'Hoolihan')
2181+
2182+
21582183
class TestOrder(unittest.TestCase):
21592184

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

0 commit comments

Comments
 (0)