Close #18989: enum members will no longer overwrite other attributes, nor be overwritten by them.
This commit is contained in:
parent
defe7f4c62
commit
101e0746d3
|
@ -154,6 +154,12 @@ return A::
|
|||
>>> Shape(2)
|
||||
<Shape.square: 2>
|
||||
|
||||
.. note::
|
||||
|
||||
Attempting to create a member with the same name as an already
|
||||
defined attribute (another member, a method, etc.) or attempting to create
|
||||
an attribute with the same name as a member is not allowed.
|
||||
|
||||
|
||||
Ensuring unique enumeration values
|
||||
----------------------------------
|
||||
|
|
36
Lib/enum.py
36
Lib/enum.py
|
@ -29,6 +29,14 @@ class _RouteClassAttributeToGetattr:
|
|||
raise AttributeError("can't delete attribute")
|
||||
|
||||
|
||||
def _is_descriptor(obj):
|
||||
"""Returns True if obj is a descriptor, False otherwise."""
|
||||
return (
|
||||
hasattr(obj, '__get__') or
|
||||
hasattr(obj, '__set__') or
|
||||
hasattr(obj, '__delete__'))
|
||||
|
||||
|
||||
def _is_dunder(name):
|
||||
"""Returns True if a __dunder__ name, False otherwise."""
|
||||
return (name[:2] == name[-2:] == '__' and
|
||||
|
@ -50,8 +58,9 @@ def _make_class_unpicklable(cls):
|
|||
cls.__reduce__ = _break_on_call_reduce
|
||||
cls.__module__ = '<unknown>'
|
||||
|
||||
|
||||
class _EnumDict(dict):
|
||||
"""Keeps track of definition order of the enum items.
|
||||
"""Track enum member order and ensure member names are not reused.
|
||||
|
||||
EnumMeta will use the names found in self._member_names as the
|
||||
enumeration member names.
|
||||
|
@ -62,11 +71,7 @@ class _EnumDict(dict):
|
|||
self._member_names = []
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Changes anything not dundered or that doesn't have __get__.
|
||||
|
||||
If a descriptor is added with the same name as an enum member, the name
|
||||
is removed from _member_names (this may leave a hole in the numerical
|
||||
sequence of values).
|
||||
"""Changes anything not dundered or not a descriptor.
|
||||
|
||||
If an enum member name is used twice, an error is raised; duplicate
|
||||
values are not checked for.
|
||||
|
@ -76,19 +81,20 @@ class _EnumDict(dict):
|
|||
"""
|
||||
if _is_sunder(key):
|
||||
raise ValueError('_names_ are reserved for future Enum use')
|
||||
elif _is_dunder(key) or hasattr(value, '__get__'):
|
||||
if key in self._member_names:
|
||||
# overwriting an enum with a method? then remove the name from
|
||||
# _member_names or it will become an enum anyway when the class
|
||||
# is created
|
||||
self._member_names.remove(key)
|
||||
else:
|
||||
if key in self._member_names:
|
||||
raise TypeError('Attempted to reuse key: %r' % key)
|
||||
elif _is_dunder(key):
|
||||
pass
|
||||
elif key in self._member_names:
|
||||
# descriptor overwriting an enum?
|
||||
raise TypeError('Attempted to reuse key: %r' % key)
|
||||
elif not _is_descriptor(value):
|
||||
if key in self:
|
||||
# enum overwriting a descriptor?
|
||||
raise TypeError('Key already defined as: %r' % self[key])
|
||||
self._member_names.append(key)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
|
||||
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
|
||||
# until EnumMeta finishes running the first time the Enum class doesn't exist.
|
||||
# This is also why there are checks in EnumMeta like `if Enum is not None`
|
||||
|
|
|
@ -228,6 +228,32 @@ class TestEnum(unittest.TestCase):
|
|||
['FALL', 'ANOTHER_SPRING'],
|
||||
)
|
||||
|
||||
def test_duplicate_name(self):
|
||||
with self.assertRaises(TypeError):
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
red = 4
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
def red(self):
|
||||
return 'red'
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
class Color(Enum):
|
||||
@property
|
||||
def red(self):
|
||||
return 'redder'
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
|
||||
def test_enum_with_value_name(self):
|
||||
class Huh(Enum):
|
||||
name = 1
|
||||
|
@ -618,17 +644,6 @@ class TestEnum(unittest.TestCase):
|
|||
self.assertIsNot(type(whatever.really), whatever)
|
||||
self.assertEqual(whatever.this.really(), 'no, not that')
|
||||
|
||||
def test_overwrite_enums(self):
|
||||
class Why(Enum):
|
||||
question = 1
|
||||
answer = 2
|
||||
propisition = 3
|
||||
def question(self):
|
||||
print(42)
|
||||
self.assertIsNot(type(Why.question), Why)
|
||||
self.assertNotIn(Why.question, Why._member_names_)
|
||||
self.assertNotIn(Why.question, Why)
|
||||
|
||||
def test_wrong_inheritance_order(self):
|
||||
with self.assertRaises(TypeError):
|
||||
class Wrong(Enum, str):
|
||||
|
|
Loading…
Reference in New Issue