Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 101e074

Browse files
committed
Close #18989: enum members will no longer overwrite other attributes, nor be overwritten by them.
1 parent defe7f4 commit 101e074

3 files changed

Lines changed: 53 additions & 26 deletions

File tree

Doc/library/enum.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,12 @@ return A::
154154
>>> Shape(2)
155155
<Shape.square: 2>
156156

157+
.. note::
158+
159+
Attempting to create a member with the same name as an already
160+
defined attribute (another member, a method, etc.) or attempting to create
161+
an attribute with the same name as a member is not allowed.
162+
157163

158164
Ensuring unique enumeration values
159165
----------------------------------

Lib/enum.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,14 @@ def __delete__(self, instance):
2929
raise AttributeError("can't delete attribute")
3030

3131

32+
def _is_descriptor(obj):
33+
"""Returns True if obj is a descriptor, False otherwise."""
34+
return (
35+
hasattr(obj, '__get__') or
36+
hasattr(obj, '__set__') or
37+
hasattr(obj, '__delete__'))
38+
39+
3240
def _is_dunder(name):
3341
"""Returns True if a __dunder__ name, False otherwise."""
3442
return (name[:2] == name[-2:] == '__' and
@@ -50,8 +58,9 @@ def _break_on_call_reduce(self):
5058
cls.__reduce__ = _break_on_call_reduce
5159
cls.__module__ = '<unknown>'
5260

61+
5362
class _EnumDict(dict):
54-
"""Keeps track of definition order of the enum items.
63+
"""Track enum member order and ensure member names are not reused.
5564
5665
EnumMeta will use the names found in self._member_names as the
5766
enumeration member names.
@@ -62,11 +71,7 @@ def __init__(self):
6271
self._member_names = []
6372

6473
def __setitem__(self, key, value):
65-
"""Changes anything not dundered or that doesn't have __get__.
66-
67-
If a descriptor is added with the same name as an enum member, the name
68-
is removed from _member_names (this may leave a hole in the numerical
69-
sequence of values).
74+
"""Changes anything not dundered or not a descriptor.
7075
7176
If an enum member name is used twice, an error is raised; duplicate
7277
values are not checked for.
@@ -76,19 +81,20 @@ def __setitem__(self, key, value):
7681
"""
7782
if _is_sunder(key):
7883
raise ValueError('_names_ are reserved for future Enum use')
79-
elif _is_dunder(key) or hasattr(value, '__get__'):
80-
if key in self._member_names:
81-
# overwriting an enum with a method? then remove the name from
82-
# _member_names or it will become an enum anyway when the class
83-
# is created
84-
self._member_names.remove(key)
85-
else:
86-
if key in self._member_names:
87-
raise TypeError('Attempted to reuse key: %r' % key)
84+
elif _is_dunder(key):
85+
pass
86+
elif key in self._member_names:
87+
# descriptor overwriting an enum?
88+
raise TypeError('Attempted to reuse key: %r' % key)
89+
elif not _is_descriptor(value):
90+
if key in self:
91+
# enum overwriting a descriptor?
92+
raise TypeError('Key already defined as: %r' % self[key])
8893
self._member_names.append(key)
8994
super().__setitem__(key, value)
9095

9196

97+
9298
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
9399
# until EnumMeta finishes running the first time the Enum class doesn't exist.
94100
# This is also why there are checks in EnumMeta like `if Enum is not None`

Lib/test/test_enum.py

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,32 @@ class Season(Enum):
228228
['FALL', 'ANOTHER_SPRING'],
229229
)
230230

231+
def test_duplicate_name(self):
232+
with self.assertRaises(TypeError):
233+
class Color(Enum):
234+
red = 1
235+
green = 2
236+
blue = 3
237+
red = 4
238+
239+
with self.assertRaises(TypeError):
240+
class Color(Enum):
241+
red = 1
242+
green = 2
243+
blue = 3
244+
def red(self):
245+
return 'red'
246+
247+
with self.assertRaises(TypeError):
248+
class Color(Enum):
249+
@property
250+
def red(self):
251+
return 'redder'
252+
red = 1
253+
green = 2
254+
blue = 3
255+
256+
231257
def test_enum_with_value_name(self):
232258
class Huh(Enum):
233259
name = 1
@@ -618,17 +644,6 @@ def really(self):
618644
self.assertIsNot(type(whatever.really), whatever)
619645
self.assertEqual(whatever.this.really(), 'no, not that')
620646

621-
def test_overwrite_enums(self):
622-
class Why(Enum):
623-
question = 1
624-
answer = 2
625-
propisition = 3
626-
def question(self):
627-
print(42)
628-
self.assertIsNot(type(Why.question), Why)
629-
self.assertNotIn(Why.question, Why._member_names_)
630-
self.assertNotIn(Why.question, Why)
631-
632647
def test_wrong_inheritance_order(self):
633648
with self.assertRaises(TypeError):
634649
class Wrong(Enum, str):

0 commit comments

Comments
 (0)