mirror of https://github.com/python/cpython.git
gh-112328: Make EnumDict usable on its own and document it (GH-123669)
Co-authored-by: Rafi <rafi.promit@gmail.com> Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <wk.cvs.github@sydorenko.org.ua> Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
This commit is contained in:
parent
3879ca0100
commit
2a66dd33df
|
@ -110,6 +110,10 @@ Module Contents
|
||||||
``KEEP`` which allows for more fine-grained control over how invalid values
|
``KEEP`` which allows for more fine-grained control over how invalid values
|
||||||
are dealt with in an enumeration.
|
are dealt with in an enumeration.
|
||||||
|
|
||||||
|
:class:`EnumDict`
|
||||||
|
|
||||||
|
A subclass of :class:`dict` for use when subclassing :class:`EnumType`.
|
||||||
|
|
||||||
:class:`auto`
|
:class:`auto`
|
||||||
|
|
||||||
Instances are replaced with an appropriate value for Enum members.
|
Instances are replaced with an appropriate value for Enum members.
|
||||||
|
@ -149,14 +153,10 @@ Module Contents
|
||||||
|
|
||||||
Return a list of all power-of-two integers contained in a flag.
|
Return a list of all power-of-two integers contained in a flag.
|
||||||
|
|
||||||
:class:`EnumDict`
|
|
||||||
|
|
||||||
A subclass of :class:`dict` for use when subclassing :class:`EnumType`.
|
|
||||||
|
|
||||||
|
|
||||||
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
|
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
|
||||||
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
|
.. versionadded:: 3.11 ``StrEnum``, ``EnumCheck``, ``ReprEnum``, ``FlagBoundary``, ``property``, ``member``, ``nonmember``, ``global_enum``, ``show_flag_values``
|
||||||
.. versionadded:: 3.14 ``EnumDict``
|
.. versionadded:: 3.13 ``EnumDict``
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -830,13 +830,23 @@ Data Types
|
||||||
|
|
||||||
.. class:: EnumDict
|
.. class:: EnumDict
|
||||||
|
|
||||||
*EnumDict* is a subclass of :class:`dict` for use when subclassing :class:`EnumType`.
|
*EnumDict* is a subclass of :class:`dict` that is used as the namespace
|
||||||
|
for defining enum classes (see :ref:`prepare`).
|
||||||
|
It is exposed to allow subclasses of :class:`EnumType` with advanced
|
||||||
|
behavior like having multiple values per member.
|
||||||
|
It should be called with the name of the enum class being created, otherwise
|
||||||
|
private names and internal classes will not be handled correctly.
|
||||||
|
|
||||||
|
Note that only the :class:`~collections.abc.MutableMapping` interface
|
||||||
|
(:meth:`~object.__setitem__` and :meth:`~dict.update`) is overridden.
|
||||||
|
It may be possible to bypass the checks using other :class:`!dict`
|
||||||
|
operations like :meth:`|= <object.__ior__>`.
|
||||||
|
|
||||||
.. attribute:: EnumDict.member_names
|
.. attribute:: EnumDict.member_names
|
||||||
|
|
||||||
Return list of member names.
|
A list of member names.
|
||||||
|
|
||||||
.. versionadded:: 3.14
|
.. versionadded:: 3.13
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -879,11 +879,13 @@ email
|
||||||
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
|
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
|
||||||
the :cve:`2023-27043` fix.)
|
the :cve:`2023-27043` fix.)
|
||||||
|
|
||||||
|
|
||||||
enum
|
enum
|
||||||
----
|
----
|
||||||
|
|
||||||
* :class:`~enum.EnumDict` has been made public in :mod:`enum` to better support
|
* :class:`~enum.EnumDict` has been made public to better support subclassing
|
||||||
subclassing :class:`~enum.EnumType`.
|
:class:`~enum.EnumType`.
|
||||||
|
|
||||||
|
|
||||||
fractions
|
fractions
|
||||||
---------
|
---------
|
||||||
|
|
10
Lib/enum.py
10
Lib/enum.py
|
@ -342,12 +342,13 @@ class EnumDict(dict):
|
||||||
EnumType will use the names found in self._member_names as the
|
EnumType will use the names found in self._member_names as the
|
||||||
enumeration member names.
|
enumeration member names.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self, cls_name=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
|
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
|
||||||
self._last_values = []
|
self._last_values = []
|
||||||
self._ignore = []
|
self._ignore = []
|
||||||
self._auto_called = False
|
self._auto_called = False
|
||||||
|
self._cls_name = cls_name
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
"""
|
"""
|
||||||
|
@ -358,7 +359,7 @@ def __setitem__(self, key, value):
|
||||||
|
|
||||||
Single underscore (sunder) names are reserved.
|
Single underscore (sunder) names are reserved.
|
||||||
"""
|
"""
|
||||||
if _is_private(self._cls_name, key):
|
if self._cls_name is not None and _is_private(self._cls_name, key):
|
||||||
# do nothing, name will be a normal attribute
|
# do nothing, name will be a normal attribute
|
||||||
pass
|
pass
|
||||||
elif _is_sunder(key):
|
elif _is_sunder(key):
|
||||||
|
@ -406,7 +407,7 @@ def __setitem__(self, key, value):
|
||||||
value = value.value
|
value = value.value
|
||||||
elif _is_descriptor(value):
|
elif _is_descriptor(value):
|
||||||
pass
|
pass
|
||||||
elif _is_internal_class(self._cls_name, value):
|
elif self._cls_name is not None and _is_internal_class(self._cls_name, value):
|
||||||
# do nothing, name will be a normal attribute
|
# do nothing, name will be a normal attribute
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
@ -478,8 +479,7 @@ def __prepare__(metacls, cls, bases, **kwds):
|
||||||
# check that previous enum members do not exist
|
# check that previous enum members do not exist
|
||||||
metacls._check_for_existing_members_(cls, bases)
|
metacls._check_for_existing_members_(cls, bases)
|
||||||
# create the namespace dict
|
# create the namespace dict
|
||||||
enum_dict = EnumDict()
|
enum_dict = EnumDict(cls)
|
||||||
enum_dict._cls_name = cls
|
|
||||||
# inherit previous flags and _generate_next_value_ function
|
# inherit previous flags and _generate_next_value_ function
|
||||||
member_type, first_enum = metacls._get_mixins_(cls, bases)
|
member_type, first_enum = metacls._get_mixins_(cls, bases)
|
||||||
if first_enum is not None:
|
if first_enum is not None:
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
from enum import Enum, EnumMeta, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
|
from enum import Enum, EnumMeta, IntEnum, StrEnum, EnumType, Flag, IntFlag, unique, auto
|
||||||
from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum
|
from enum import STRICT, CONFORM, EJECT, KEEP, _simple_enum, _test_simple_enum
|
||||||
from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum
|
from enum import verify, UNIQUE, CONTINUOUS, NAMED_FLAGS, ReprEnum
|
||||||
from enum import member, nonmember, _iter_bits_lsb
|
from enum import member, nonmember, _iter_bits_lsb, EnumDict
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
|
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
|
||||||
from test import support
|
from test import support
|
||||||
|
@ -5440,6 +5440,37 @@ def test_convert_repr_and_str(self):
|
||||||
self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5')
|
self.assertEqual(format(test_type.CONVERT_STRING_TEST_NAME_A), '5')
|
||||||
|
|
||||||
|
|
||||||
|
class TestEnumDict(unittest.TestCase):
|
||||||
|
def test_enum_dict_in_metaclass(self):
|
||||||
|
"""Test that EnumDict is usable as a class namespace"""
|
||||||
|
class Meta(type):
|
||||||
|
@classmethod
|
||||||
|
def __prepare__(metacls, cls, bases, **kwds):
|
||||||
|
return EnumDict(cls)
|
||||||
|
|
||||||
|
class MyClass(metaclass=Meta):
|
||||||
|
a = 1
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
a = 2 # duplicate
|
||||||
|
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
_a_sunder_ = 3
|
||||||
|
|
||||||
|
def test_enum_dict_standalone(self):
|
||||||
|
"""Test that EnumDict is usable on its own"""
|
||||||
|
enumdict = EnumDict()
|
||||||
|
enumdict['a'] = 1
|
||||||
|
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
enumdict['a'] = 'other value'
|
||||||
|
|
||||||
|
# Only MutableMapping interface is overridden for now.
|
||||||
|
# If this stops passing, update the documentation.
|
||||||
|
enumdict |= {'a': 'other value'}
|
||||||
|
self.assertEqual(enumdict['a'], 'other value')
|
||||||
|
|
||||||
|
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
def enum_dir(cls):
|
def enum_dir(cls):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
:class:`enum.EnumDict` can now be used without resorting to private API.
|
Loading…
Reference in New Issue