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
This commit is contained in:
parent
f52237400b
commit
0fb9fadd3b
83
Lib/enum.py
83
Lib/enum.py
|
@ -165,9 +165,11 @@ class EnumMeta(type):
|
||||||
enum_class._member_map_ = {} # name->value map
|
enum_class._member_map_ = {} # name->value map
|
||||||
enum_class._member_type_ = member_type
|
enum_class._member_type_ = member_type
|
||||||
|
|
||||||
# save attributes from super classes so we know if we can take
|
# save DynamicClassAttribute attributes from super classes so we know
|
||||||
# the shortcut of storing members in the class dict
|
# 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__}
|
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.
|
# Reverse value->name map for hashable values.
|
||||||
enum_class._value2member_map_ = {}
|
enum_class._value2member_map_ = {}
|
||||||
|
@ -227,7 +229,7 @@ class EnumMeta(type):
|
||||||
enum_class._member_names_.append(member_name)
|
enum_class._member_names_.append(member_name)
|
||||||
# performance boost for any member that would not shadow
|
# performance boost for any member that would not shadow
|
||||||
# a DynamicClassAttribute
|
# a DynamicClassAttribute
|
||||||
if member_name not in base_attributes:
|
if member_name not in dynamic_attributes:
|
||||||
setattr(enum_class, member_name, enum_member)
|
setattr(enum_class, member_name, enum_member)
|
||||||
# now add to _member_map_
|
# now add to _member_map_
|
||||||
enum_class._member_map_[member_name] = enum_member
|
enum_class._member_map_[member_name] = enum_member
|
||||||
|
@ -428,6 +430,45 @@ class EnumMeta(type):
|
||||||
|
|
||||||
return enum_class
|
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
|
@staticmethod
|
||||||
def _get_mixins_(bases):
|
def _get_mixins_(bases):
|
||||||
"""Returns the type for creating enum members, and the first inherited
|
"""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."""
|
"""The value of the Enum member."""
|
||||||
return self._value_
|
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):
|
class IntEnum(int, Enum):
|
||||||
"""Enum where members are also (and must be) ints"""
|
"""Enum where members are also (and must be) ints"""
|
||||||
|
|
|
@ -5,19 +5,19 @@ from enum import IntEnum as _IntEnum
|
||||||
|
|
||||||
_globals = globals()
|
_globals = globals()
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'Signals', __name__,
|
'Signals', __name__,
|
||||||
lambda name:
|
lambda name:
|
||||||
name.isupper()
|
name.isupper()
|
||||||
and (name.startswith('SIG') and not name.startswith('SIG_'))
|
and (name.startswith('SIG') and not name.startswith('SIG_'))
|
||||||
or name.startswith('CTRL_'))
|
or name.startswith('CTRL_'))
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'Handlers', __name__,
|
'Handlers', __name__,
|
||||||
lambda name: name in ('SIG_DFL', 'SIG_IGN'))
|
lambda name: name in ('SIG_DFL', 'SIG_IGN'))
|
||||||
|
|
||||||
if 'pthread_sigmask' in _globals:
|
if 'pthread_sigmask' in _globals:
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'Sigmasks', __name__,
|
'Sigmasks', __name__,
|
||||||
lambda name: name in ('SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'))
|
lambda name: name in ('SIG_BLOCK', 'SIG_UNBLOCK', 'SIG_SETMASK'))
|
||||||
|
|
||||||
|
|
|
@ -70,22 +70,22 @@ __all__.extend(os._get_exports_list(_socket))
|
||||||
# in this module understands the enums and translates them back from integers
|
# in this module understands the enums and translates them back from integers
|
||||||
# where needed (e.g. .family property of a socket object).
|
# where needed (e.g. .family property of a socket object).
|
||||||
|
|
||||||
IntEnum._convert(
|
IntEnum._convert_(
|
||||||
'AddressFamily',
|
'AddressFamily',
|
||||||
__name__,
|
__name__,
|
||||||
lambda C: C.isupper() and C.startswith('AF_'))
|
lambda C: C.isupper() and C.startswith('AF_'))
|
||||||
|
|
||||||
IntEnum._convert(
|
IntEnum._convert_(
|
||||||
'SocketKind',
|
'SocketKind',
|
||||||
__name__,
|
__name__,
|
||||||
lambda C: C.isupper() and C.startswith('SOCK_'))
|
lambda C: C.isupper() and C.startswith('SOCK_'))
|
||||||
|
|
||||||
IntFlag._convert(
|
IntFlag._convert_(
|
||||||
'MsgFlag',
|
'MsgFlag',
|
||||||
__name__,
|
__name__,
|
||||||
lambda C: C.isupper() and C.startswith('MSG_'))
|
lambda C: C.isupper() and C.startswith('MSG_'))
|
||||||
|
|
||||||
IntFlag._convert(
|
IntFlag._convert_(
|
||||||
'AddressInfo',
|
'AddressInfo',
|
||||||
__name__,
|
__name__,
|
||||||
lambda C: C.isupper() and C.startswith('AI_'))
|
lambda C: C.isupper() and C.startswith('AI_'))
|
||||||
|
|
12
Lib/ssl.py
12
Lib/ssl.py
|
@ -119,32 +119,32 @@ from _ssl import (
|
||||||
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
|
from _ssl import _DEFAULT_CIPHERS, _OPENSSL_API_VERSION
|
||||||
|
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'_SSLMethod', __name__,
|
'_SSLMethod', __name__,
|
||||||
lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23',
|
lambda name: name.startswith('PROTOCOL_') and name != 'PROTOCOL_SSLv23',
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
_IntFlag._convert(
|
_IntFlag._convert_(
|
||||||
'Options', __name__,
|
'Options', __name__,
|
||||||
lambda name: name.startswith('OP_'),
|
lambda name: name.startswith('OP_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'AlertDescription', __name__,
|
'AlertDescription', __name__,
|
||||||
lambda name: name.startswith('ALERT_DESCRIPTION_'),
|
lambda name: name.startswith('ALERT_DESCRIPTION_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'SSLErrorNumber', __name__,
|
'SSLErrorNumber', __name__,
|
||||||
lambda name: name.startswith('SSL_ERROR_'),
|
lambda name: name.startswith('SSL_ERROR_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
_IntFlag._convert(
|
_IntFlag._convert_(
|
||||||
'VerifyFlags', __name__,
|
'VerifyFlags', __name__,
|
||||||
lambda name: name.startswith('VERIFY_'),
|
lambda name: name.startswith('VERIFY_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
||||||
_IntEnum._convert(
|
_IntEnum._convert_(
|
||||||
'VerifyMode', __name__,
|
'VerifyMode', __name__,
|
||||||
lambda name: name.startswith('CERT_'),
|
lambda name: name.startswith('CERT_'),
|
||||||
source=_ssl)
|
source=_ssl)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import enum
|
import enum
|
||||||
import inspect
|
import inspect
|
||||||
import pydoc
|
import pydoc
|
||||||
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
@ -1511,6 +1512,23 @@ class TestEnum(unittest.TestCase):
|
||||||
yellow = 6
|
yellow = 6
|
||||||
self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!')
|
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):
|
def test_no_duplicates(self):
|
||||||
class UniqueEnum(Enum):
|
class UniqueEnum(Enum):
|
||||||
|
@ -2668,7 +2686,7 @@ CONVERT_TEST_NAME_F = 5
|
||||||
|
|
||||||
class TestIntEnumConvert(unittest.TestCase):
|
class TestIntEnumConvert(unittest.TestCase):
|
||||||
def test_convert_value_lookup_priority(self):
|
def test_convert_value_lookup_priority(self):
|
||||||
test_type = enum.IntEnum._convert(
|
test_type = enum.IntEnum._convert_(
|
||||||
'UnittestConvert',
|
'UnittestConvert',
|
||||||
('test.test_enum', '__main__')[__name__=='__main__'],
|
('test.test_enum', '__main__')[__name__=='__main__'],
|
||||||
filter=lambda x: x.startswith('CONVERT_TEST_'))
|
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')
|
self.assertEqual(test_type(5).name, 'CONVERT_TEST_NAME_A')
|
||||||
|
|
||||||
def test_convert(self):
|
def test_convert(self):
|
||||||
test_type = enum.IntEnum._convert(
|
test_type = enum.IntEnum._convert_(
|
||||||
'UnittestConvert',
|
'UnittestConvert',
|
||||||
('test.test_enum', '__main__')[__name__=='__main__'],
|
('test.test_enum', '__main__')[__name__=='__main__'],
|
||||||
filter=lambda x: x.startswith('CONVERT_TEST_'))
|
filter=lambda x: x.startswith('CONVERT_TEST_'))
|
||||||
|
@ -2694,6 +2712,24 @@ class TestIntEnumConvert(unittest.TestCase):
|
||||||
if name[0:2] not in ('CO', '__')],
|
if name[0:2] not in ('CO', '__')],
|
||||||
[], msg='Names other than CONVERT_TEST_* found.')
|
[], 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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -1178,6 +1178,7 @@ Piet van Oostrum
|
||||||
Tomas Oppelstrup
|
Tomas Oppelstrup
|
||||||
Jason Orendorff
|
Jason Orendorff
|
||||||
Bastien Orivel
|
Bastien Orivel
|
||||||
|
orlnub123
|
||||||
Douglas Orr
|
Douglas Orr
|
||||||
William Orr
|
William Orr
|
||||||
Michele Orrù
|
Michele Orrù
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Move ``Enum._convert`` to ``EnumMeta._convert_`` and fix enum members getting
|
||||||
|
shadowed by parent attributes.
|
Loading…
Reference in New Issue