From 2324bdc5eb23e563389400f76ecdea5d5c5a1490 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Wed, 9 Dec 2020 17:12:11 -0800 Subject: [PATCH 1/3] 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 (cherry picked from commit 7cf0aad96d1d20f07d7f0e374885f327c2d5ff27) Co-authored-by: Ethan Furman --- Doc/library/enum.rst | 9 +++++++ Lib/enum.py | 19 +++++++++++++- Lib/test/test_enum.py | 25 +++++++++++++++++++ .../2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst | 2 ++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index a3c51655576ba5..276665b0bccf5f 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -1121,6 +1121,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 """""""""""""""""""" diff --git a/Lib/enum.py b/Lib/enum.py index b14da088f33bf3..a4c39bac189bd8 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -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. @@ -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_', @@ -146,6 +162,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: diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index ccc2cbe1ec14b5..e76eaf456bf23d 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1135,6 +1135,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: @@ -2037,6 +2038,30 @@ def test_empty_globals(self): exec(code, global_ns, local_ls) + @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): diff --git a/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst b/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst new file mode 100644 index 00000000000000..813139dfe5d005 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst @@ -0,0 +1,2 @@ +Enum: private names do not become members / do not generate errors -- they +remain normal attributes From aed4bdeec903f4acddce1dd19dfd472a3767764f Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Mon, 14 Dec 2020 14:56:29 -0800 Subject: [PATCH 2/3] [Enum] issue deprecation warning for private names --- Lib/enum.py | 11 ++++++++--- Lib/test/test_enum.py | 7 ------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index a4c39bac189bd8..54d0f1b8c4cc24 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -95,9 +95,14 @@ def __setitem__(self, key, value): Single underscore (sunder) names are reserved. """ if _is_private(self._cls_name, key): - # do nothing, name will be a normal attribute - pass - elif _is_sunder(key): + import warnings + warnings.warn( + "private variables, such as %r, will be normal attributes in 3.10" + % (key, ), + DeprecationWarning, + stacklevel=2, + ) + if _is_sunder(key): if key not in ( '_order_', '_create_pseudo_member_', '_generate_next_value_', '_missing_', '_ignore_', diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index e76eaf456bf23d..2c463a919c3f86 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2054,13 +2054,6 @@ class Private(Enum): 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): From c27762adab3f2b2d4006c21e3ce58228ba164777 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Mon, 14 Dec 2020 15:11:31 -0800 Subject: [PATCH 3/3] correct docs --- Doc/library/enum.rst | 6 +++--- .../next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst index 276665b0bccf5f..4ecca209d87b3d 100644 --- a/Doc/library/enum.rst +++ b/Doc/library/enum.rst @@ -1125,9 +1125,9 @@ and raise an error if the two do not match:: _Private__names """"""""""""""" -Private names are not converted to Enum members, but remain normal attributes. - -.. versionchanged:: 3.10 +Private names will be normal attributes in Python 3.10 instead of either an error +or a member (depending on if the name ends with an underscore). Using these names +in 3.9 will issue a :exc:`DeprecationWarning`. ``Enum`` member type diff --git a/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst b/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst index 813139dfe5d005..e99294d9430d1a 100644 --- a/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst +++ b/Misc/NEWS.d/next/Library/2020-12-09-10-59-16.bpo-42517.FKEVcZ.rst @@ -1,2 +1,2 @@ -Enum: private names do not become members / do not generate errors -- they -remain normal attributes +Enum: private names will raise a DeprecationWarning; in 3.10 they will +become normal attributes