mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
Close #18989: enum members will no longer overwrite other attributes, nor be overwritten by them.
This commit is contained in:
parent
defe7f4c62
commit
101e0746d3
3 changed files with 53 additions and 26 deletions
|
@ -154,6 +154,12 @@ return A::
|
||||||
>>> Shape(2)
|
>>> Shape(2)
|
||||||
<Shape.square: 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
|
Ensuring unique enumeration values
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
36
Lib/enum.py
36
Lib/enum.py
|
@ -29,6 +29,14 @@ class _RouteClassAttributeToGetattr:
|
||||||
raise AttributeError("can't delete attribute")
|
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):
|
def _is_dunder(name):
|
||||||
"""Returns True if a __dunder__ name, False otherwise."""
|
"""Returns True if a __dunder__ name, False otherwise."""
|
||||||
return (name[:2] == name[-2:] == '__' and
|
return (name[:2] == name[-2:] == '__' and
|
||||||
|
@ -50,8 +58,9 @@ def _make_class_unpicklable(cls):
|
||||||
cls.__reduce__ = _break_on_call_reduce
|
cls.__reduce__ = _break_on_call_reduce
|
||||||
cls.__module__ = '<unknown>'
|
cls.__module__ = '<unknown>'
|
||||||
|
|
||||||
|
|
||||||
class _EnumDict(dict):
|
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
|
EnumMeta will use the names found in self._member_names as the
|
||||||
enumeration member names.
|
enumeration member names.
|
||||||
|
@ -62,11 +71,7 @@ class _EnumDict(dict):
|
||||||
self._member_names = []
|
self._member_names = []
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
"""Changes anything not dundered or that doesn't have __get__.
|
"""Changes anything not dundered or not a descriptor.
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
If an enum member name is used twice, an error is raised; duplicate
|
If an enum member name is used twice, an error is raised; duplicate
|
||||||
values are not checked for.
|
values are not checked for.
|
||||||
|
@ -76,19 +81,20 @@ class _EnumDict(dict):
|
||||||
"""
|
"""
|
||||||
if _is_sunder(key):
|
if _is_sunder(key):
|
||||||
raise ValueError('_names_ are reserved for future Enum use')
|
raise ValueError('_names_ are reserved for future Enum use')
|
||||||
elif _is_dunder(key) or hasattr(value, '__get__'):
|
elif _is_dunder(key):
|
||||||
if key in self._member_names:
|
pass
|
||||||
# overwriting an enum with a method? then remove the name from
|
elif key in self._member_names:
|
||||||
# _member_names or it will become an enum anyway when the class
|
# descriptor overwriting an enum?
|
||||||
# is created
|
raise TypeError('Attempted to reuse key: %r' % key)
|
||||||
self._member_names.remove(key)
|
elif not _is_descriptor(value):
|
||||||
else:
|
if key in self:
|
||||||
if key in self._member_names:
|
# enum overwriting a descriptor?
|
||||||
raise TypeError('Attempted to reuse key: %r' % key)
|
raise TypeError('Key already defined as: %r' % self[key])
|
||||||
self._member_names.append(key)
|
self._member_names.append(key)
|
||||||
super().__setitem__(key, value)
|
super().__setitem__(key, value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
|
# 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.
|
# 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`
|
# 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'],
|
['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):
|
def test_enum_with_value_name(self):
|
||||||
class Huh(Enum):
|
class Huh(Enum):
|
||||||
name = 1
|
name = 1
|
||||||
|
@ -618,17 +644,6 @@ class TestEnum(unittest.TestCase):
|
||||||
self.assertIsNot(type(whatever.really), whatever)
|
self.assertIsNot(type(whatever.really), whatever)
|
||||||
self.assertEqual(whatever.this.really(), 'no, not that')
|
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):
|
def test_wrong_inheritance_order(self):
|
||||||
with self.assertRaises(TypeError):
|
with self.assertRaises(TypeError):
|
||||||
class Wrong(Enum, str):
|
class Wrong(Enum, str):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue