summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authororlnub123 <orlnub123@gmail.com>2018-09-12 17:28:53 (GMT)
committerEthan Furman <ethan@stoneleaf.us>2018-09-12 17:28:53 (GMT)
commit0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979 (patch)
tree0a53251ce3c6655e1a291d2c668e74bbb1318ea7 /Lib
parentf52237400b9960d434c5d0676a3479b8c1e8c869 (diff)
downloadcpython-0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979.zip
cpython-0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979.tar.gz
cpython-0fb9fadd3b3e9e3698647e0b92d49b0b7aacd979.tar.bz2
bpo-34282: Fix Enum._convert shadowing members named _convert (GH-8568)
* Fix enum members getting shadowed by parent attributes * Move Enum._convert to EnumMeta._convert_ * Deprecate _convert
Diffstat (limited to 'Lib')
-rw-r--r--Lib/enum.py83
-rw-r--r--Lib/signal.py6
-rw-r--r--Lib/socket.py8
-rw-r--r--Lib/ssl.py12
-rw-r--r--Lib/test/test_enum.py40
5 files changed, 96 insertions, 53 deletions
diff --git a/Lib/enum.py b/Lib/enum.py
index 9d1aef3..0839671 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -165,9 +165,11 @@ class EnumMeta(type):
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 @@ class EnumMeta(type):
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
@@ -428,6 +430,45 @@ class EnumMeta(type):
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
+
+ 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, **kwargs)
+
@staticmethod
def _get_mixins_(bases):
"""Returns the type for creating enum members, and the first inherited
@@ -613,40 +654,6 @@ class Enum(metaclass=EnumMeta):
"""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"""
diff --git a/Lib/signal.py b/Lib/signal.py
index 826b62c..d4a6d6f 100644
--- a/Lib/signal.py
+++ b/Lib/signal.py
@@ -5,19 +5,19 @@ from enum import IntEnum as _IntEnum
_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 cfa605a..385844b 100644
--- a/Lib/socket.py
+++ b/Lib/socket.py
@@ -70,22 +70,22 @@ __all__.extend(os._get_exports_list(_socket))
# 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 fdd1615..fa7c152 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -119,32 +119,32 @@ from _ssl import (
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 68483e6..c04d03f 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
@@ -1511,6 +1512,23 @@ class TestEnum(unittest.TestCase):
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):
@@ -2668,7 +2686,7 @@ CONVERT_TEST_NAME_F = 5
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_'))
@@ -2678,7 +2696,7 @@ class TestIntEnumConvert(unittest.TestCase):
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_'))
@@ -2694,6 +2712,24 @@ class TestIntEnumConvert(unittest.TestCase):
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()