From 787f5b7ac7dd42ea61801bdb376f4adcada9cde4 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Mon, 30 Jul 2018 20:01:19 +0300 Subject: [PATCH 01/10] Move _convert to the metaclass --- Lib/enum.py | 67 ++++++++++++++++++++++++++--------------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 04d8ec1fa872f8..e8551e4082a454 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -424,6 +424,39 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s return enum_class + def _convert(cls, name, module, filter, source=None): + """ + Create a new Enum subclass that replaces a collection of global constants + """ + # convert all constants from source (or module) that pass filter() to + # a new Enum called name, and export the enum and its members back to + # module; + # also, replace the __reduce_ex__ method so unpickling works in + # previous Python versions + module_globals = vars(sys.modules[module]) + if source: + source = vars(source) + else: + source = module_globals + # _value2member_map_ is populated in the same order every time + # for a consistent reverse mapping of number to name when there + # are multiple names for the same number. + members = [ + (name, value) + for name, value in source.items() + if filter(name)] + try: + # sort by value + members.sort(key=lambda t: (t[1], t[0])) + except TypeError: + # unless some values aren't comparable, in which case sort by name + members.sort(key=lambda t: t[0]) + cls = cls(name, members, module=module) + cls.__reduce_ex__ = _reduce_ex_by_name + module_globals.update(cls.__members__) + module_globals[name] = cls + return cls + @staticmethod def _get_mixins_(bases): """Returns the type for creating enum members, and the first inherited @@ -609,40 +642,6 @@ def value(self): """The value of the Enum member.""" return self._value_ - @classmethod - def _convert(cls, name, module, filter, source=None): - """ - Create a new Enum subclass that replaces a collection of global constants - """ - # convert all constants from source (or module) that pass filter() to - # a new Enum called name, and export the enum and its members back to - # module; - # also, replace the __reduce_ex__ method so unpickling works in - # previous Python versions - module_globals = vars(sys.modules[module]) - if source: - source = vars(source) - else: - source = module_globals - # _value2member_map_ is populated in the same order every time - # for a consistent reverse mapping of number to name when there - # are multiple names for the same number. - members = [ - (name, value) - for name, value in source.items() - if filter(name)] - try: - # sort by value - members.sort(key=lambda t: (t[1], t[0])) - except TypeError: - # unless some values aren't comparable, in which case sort by name - members.sort(key=lambda t: t[0]) - cls = cls(name, members, module=module) - cls.__reduce_ex__ = _reduce_ex_by_name - module_globals.update(cls.__members__) - module_globals[name] = cls - return cls - class IntEnum(int, Enum): """Enum where members are also (and must be) ints""" From 286496f86a061d5adf404f3202303e54bcf55706 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Mon, 30 Jul 2018 20:04:19 +0300 Subject: [PATCH 02/10] Rename _convert to _convert_ --- Lib/enum.py | 2 +- Lib/signal.py | 6 +++--- Lib/socket.py | 8 ++++---- Lib/ssl.py | 12 ++++++------ Lib/test/test_enum.py | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index e8551e4082a454..81d374243720ee 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -424,7 +424,7 @@ def _create_(cls, class_name, names, *, module=None, qualname=None, type=None, s return enum_class - def _convert(cls, name, module, filter, source=None): + def _convert_(cls, name, module, filter, source=None): """ Create a new Enum subclass that replaces a collection of global constants """ diff --git a/Lib/signal.py b/Lib/signal.py index 826b62cf596ccf..d4a6d6fe2ada8b 100644 --- a/Lib/signal.py +++ b/Lib/signal.py @@ -5,19 +5,19 @@ _globals = globals() -_IntEnum._convert( +_IntEnum._convert_( 'Signals', __name__, lambda name: name.isupper() and (name.startswith('SIG') and not name.startswith('SIG_')) or name.startswith('CTRL_')) -_IntEnum._convert( +_IntEnum._convert_( 'Handlers', __name__, lambda name: name in ('SIG_DFL', 'SIG_IGN')) if 'pthread_sigmask' in _globals: - _IntEnum._convert( + _IntEnum._convert_( 'Sigmasks', __name__, lambda name: name in ('SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK')) diff --git a/Lib/socket.py b/Lib/socket.py index cfa605a22ada92..385844b5853202 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -70,22 +70,22 @@ # in this module understands the enums and translates them back from integers # where needed (e.g. .family property of a socket object). -IntEnum._convert( +IntEnum._convert_( 'AddressFamily', __name__, lambda C: C.isupper() and C.startswith('AF_')) -IntEnum._convert( +IntEnum._convert_( 'SocketKind', __name__, lambda C: C.isupper() and C.startswith('SOCK_')) -IntFlag._convert( +IntFlag._convert_( 'MsgFlag', __name__, lambda C: C.isupper() and C.startswith('MSG_')) -IntFlag._convert( +IntFlag._convert_( 'AddressInfo', __name__, lambda C: C.isupper() and C.startswith('AI_')) diff --git a/Lib/ssl.py b/Lib/ssl.py index fdd1615744347e..fa7c152ade911f 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -119,32 +119,32 @@ from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION -_IntEnum._convert( +_IntEnum._convert_( '_SSLMethod', __name__, lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23', source=_ssl) -_IntFlag._convert( +_IntFlag._convert_( 'Options', __name__, lambda name: name.startswith('OP_'), source=_ssl) -_IntEnum._convert( +_IntEnum._convert_( 'AlertDescription', __name__, lambda name: name.startswith('ALERT_DESCRIPTION_'), source=_ssl) -_IntEnum._convert( +_IntEnum._convert_( 'SSLErrorNumber', __name__, lambda name: name.startswith('SSL_ERROR_'), source=_ssl) -_IntFlag._convert( +_IntFlag._convert_( 'VerifyFlags', __name__, lambda name: name.startswith('VERIFY_'), source=_ssl) -_IntEnum._convert( +_IntEnum._convert_( 'VerifyMode', __name__, lambda name: name.startswith('CERT_'), source=_ssl) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 97559712b1dc2c..38fa3f10dd8e2b 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -2619,7 +2619,7 @@ def test__all__(self): class TestIntEnumConvert(unittest.TestCase): def test_convert_value_lookup_priority(self): - test_type = enum.IntEnum._convert( + test_type = enum.IntEnum._convert_( 'UnittestConvert', ('test.test_enum', '__main__')[__name__=='__main__'], filter=lambda x: x.startswith('CONVERT_TEST_')) @@ -2629,7 +2629,7 @@ def test_convert_value_lookup_priority(self): self.assertEqual(test_type(5).name, 'CONVERT_TEST_NAME_A') def test_convert(self): - test_type = enum.IntEnum._convert( + test_type = enum.IntEnum._convert_( 'UnittestConvert', ('test.test_enum', '__main__')[__name__=='__main__'], filter=lambda x: x.startswith('CONVERT_TEST_')) From ff098f3c1814f105b2f3431882bc65d98ad8c2be Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Fri, 31 Aug 2018 09:28:19 +0300 Subject: [PATCH 03/10] Add news entry --- .../NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst diff --git a/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst b/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst new file mode 100644 index 00000000000000..499baf6c2c0614 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst @@ -0,0 +1 @@ +Fix ``Enum._convert`` shadowing members named _convert. From 5aaf6e2aee9ad0ed97ab0afadf454651f46d74aa Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Sat, 1 Sep 2018 12:35:38 +0300 Subject: [PATCH 04/10] Deprecate _convert --- Lib/enum.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Lib/enum.py b/Lib/enum.py index 81d374243720ee..a00b0093e9388c 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -457,6 +457,12 @@ def _convert_(cls, name, module, filter, source=None): module_globals[name] = cls return cls + def _convert(cls, *args): + import warnings + warnings.warn("_convert is deprecated and will be removed in 3.9, use " + "_convert_ instead.", DeprecationWarning, stacklevel=2) + return cls._convert_(*args) + @staticmethod def _get_mixins_(bases): """Returns the type for creating enum members, and the first inherited From 6062ad26a202a08a2dcfdc0315938c39e0ce1dd3 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Sat, 1 Sep 2018 12:41:12 +0300 Subject: [PATCH 05/10] Allow _convert to take keyword arguments --- Lib/enum.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index a00b0093e9388c..3f7afe43791051 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -457,11 +457,11 @@ def _convert_(cls, name, module, filter, source=None): module_globals[name] = cls return cls - def _convert(cls, *args): + def _convert(cls, *args, **kwargs): import warnings warnings.warn("_convert is deprecated and will be removed in 3.9, use " "_convert_ instead.", DeprecationWarning, stacklevel=2) - return cls._convert_(*args) + return cls._convert_(*args, **kwargs) @staticmethod def _get_mixins_(bases): From 48d2eb736de986ffd4233ab99f12c068dd3435d4 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Sat, 8 Sep 2018 13:43:35 +0300 Subject: [PATCH 06/10] Fix enum members getting shadowed by parent attributes --- Lib/enum.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Lib/enum.py b/Lib/enum.py index 3f7afe43791051..2e5d078210db35 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -165,9 +165,11 @@ def __new__(metacls, cls, bases, classdict): enum_class._member_map_ = {} # name->value map enum_class._member_type_ = member_type - # save attributes from super classes so we know if we can take - # the shortcut of storing members in the class dict - base_attributes = {a for b in enum_class.mro() for a in b.__dict__} + # save DynamicClassAttribute attributes from super classes so we know + # if we can take the shortcut of storing members in the class dict + dynamic_attributes = {k for c in enum_class.mro() + for k, v in c.__dict__.items() + if isinstance(v, DynamicClassAttribute)} # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} @@ -227,7 +229,7 @@ def __new__(metacls, cls, bases, classdict): enum_class._member_names_.append(member_name) # performance boost for any member that would not shadow # a DynamicClassAttribute - if member_name not in base_attributes: + if member_name not in dynamic_attributes: setattr(enum_class, member_name, enum_member) # now add to _member_map_ enum_class._member_map_[member_name] = enum_member From 8910b3f219f2c70a5e3ee5cf32ea6f568e004032 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Sat, 8 Sep 2018 13:45:24 +0300 Subject: [PATCH 07/10] Add tests --- Lib/test/test_enum.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 38fa3f10dd8e2b..7866b528d2e352 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1508,6 +1508,23 @@ class MoreColor(Color): yellow = 6 self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!') + def test_subclass_duplicate_name(self): + class Base(Enum): + def test(self): + pass + class Test(Base): + test = 1 + self.assertIs(type(Test.test), Test) + + def test_subclass_duplicate_name_dynamic(self): + from types import DynamicClassAttribute + class Base(Enum): + @DynamicClassAttribute + def test(self): + return 'dynamic' + class Test(Base): + test = 1 + self.assertEqual(Test.test.test, 'dynamic') def test_no_duplicates(self): class UniqueEnum(Enum): From 3219dbb570babc5c60627cc01d0ea7e6fc549962 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Sat, 8 Sep 2018 13:56:15 +0300 Subject: [PATCH 08/10] Update news entry --- .../next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst b/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst index 499baf6c2c0614..79f56f124a3f2b 100644 --- a/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst +++ b/Misc/NEWS.d/next/Library/2018-08-31-06-28-03.bpo-34282.ztyXH8.rst @@ -1 +1,2 @@ -Fix ``Enum._convert`` shadowing members named _convert. +Move ``Enum._convert`` to ``EnumMeta._convert_`` and fix enum members getting +shadowed by parent attributes. From 0d87e69e3b779a59785be8efb279a5cce015c1e8 Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Wed, 12 Sep 2018 18:38:44 +0300 Subject: [PATCH 09/10] Add _convert tests --- Lib/test/test_enum.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 7866b528d2e352..b0b96baacf61d3 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -1,6 +1,7 @@ import enum import inspect import pydoc +import sys import unittest import threading from collections import OrderedDict @@ -2662,6 +2663,24 @@ def test_convert(self): if name[0:2] not in ('CO', '__')], [], msg='Names other than CONVERT_TEST_* found.') + @unittest.skipUnless(sys.version_info[:2] == (3, 8), + '_convert was deprecated in 3.8') + def test_convert_warn(self): + with self.assertWarns(DeprecationWarning): + enum.IntEnum._convert( + 'UnittestConvert', + ('test.test_enum', '__main__')[__name__=='__main__'], + filter=lambda x: x.startswith('CONVERT_TEST_')) + + @unittest.skipUnless(sys.version_info >= (3, 9), + '_convert was removed in 3.9') + def test_convert_raise(self): + with self.assertRaises(AttributeError): + enum.IntEnum._convert( + 'UnittestConvert', + ('test.test_enum', '__main__')[__name__=='__main__'], + filter=lambda x: x.startswith('CONVERT_TEST_')) + if __name__ == '__main__': unittest.main() From 42b9aa47b3ddc22b0ec067ee13473f6d14996e6d Mon Sep 17 00:00:00 2001 From: orlnub123 <30984274+orlnub123@users.noreply.github.com> Date: Wed, 12 Sep 2018 18:40:22 +0300 Subject: [PATCH 10/10] Add myself to ACKS --- Misc/ACKS | 1 + 1 file changed, 1 insertion(+) diff --git a/Misc/ACKS b/Misc/ACKS index 2cf5e10dd141db..c52d14bc7b2925 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -1173,6 +1173,7 @@ Piet van Oostrum Tomas Oppelstrup Jason Orendorff Bastien Orivel +orlnub123 Douglas Orr William Orr Michele OrrĂ¹