mirror of https://github.com/python/cpython
gh-112328: [Enum] Make some private attributes public. (GH-112514)
* [Enum] Make some private attributes public. - ``_EnumDict`` --> ``EnumDict`` - ``EnumDict._member_names`` --> ``EnumDict.member_names`` - ``Enum._add_alias_`` - ``Enum._add_value_alias_`` --------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com> Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
This commit is contained in:
parent
563ccded6e
commit
de6bca9564
|
@ -868,7 +868,7 @@ Others
|
|||
While :class:`IntEnum` is part of the :mod:`enum` module, it would be very
|
||||
simple to implement independently::
|
||||
|
||||
class IntEnum(int, Enum):
|
||||
class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum
|
||||
pass
|
||||
|
||||
This demonstrates how similar derived enumerations can be defined; for example
|
||||
|
@ -876,8 +876,8 @@ a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`.
|
|||
|
||||
Some rules:
|
||||
|
||||
1. When subclassing :class:`Enum`, mix-in types must appear before
|
||||
:class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum`
|
||||
1. When subclassing :class:`Enum`, mix-in types must appear before the
|
||||
:class:`Enum` class itself in the sequence of bases, as in the :class:`IntEnum`
|
||||
example above.
|
||||
2. Mix-in types must be subclassable. For example, :class:`bool` and
|
||||
:class:`range` are not subclassable and will throw an error during Enum
|
||||
|
@ -961,30 +961,34 @@ all the members are created it is no longer used.
|
|||
Supported ``_sunder_`` names
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
- ``_name_`` -- name of the member
|
||||
- ``_value_`` -- value of the member; can be set / modified in ``__new__``
|
||||
- :attr:`~Enum._name_` -- name of the member
|
||||
- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__``
|
||||
- :meth:`~Enum._missing_` -- a lookup function used when a value is not found;
|
||||
may be overridden
|
||||
- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a
|
||||
:class:`str`, that will not be transformed into members, and will be removed
|
||||
from the final class
|
||||
- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for
|
||||
an enum member; may be overridden
|
||||
- :meth:`~Enum._add_alias_` -- adds a new name as an alias to an existing
|
||||
member.
|
||||
- :meth:`~Enum._add_value_alias_` -- adds a new value as an alias to an
|
||||
existing member. See `MultiValueEnum`_ for an example.
|
||||
|
||||
- ``_missing_`` -- a lookup function used when a value is not found; may be
|
||||
overridden
|
||||
- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`,
|
||||
that will not be transformed into members, and will be removed from the final
|
||||
class
|
||||
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
|
||||
(class attribute, removed during class creation)
|
||||
- ``_generate_next_value_`` -- used by the `Functional API`_ and by
|
||||
:class:`auto` to get an appropriate value for an enum member; may be
|
||||
overridden
|
||||
.. note::
|
||||
|
||||
.. note::
|
||||
For standard :class:`Enum` classes the next value chosen is the highest
|
||||
value seen incremented by one.
|
||||
|
||||
For standard :class:`Enum` classes the next value chosen is the last value seen
|
||||
incremented by one.
|
||||
For :class:`Flag` classes the next value chosen will be the next highest
|
||||
power-of-two.
|
||||
|
||||
For :class:`Flag` classes the next value chosen will be the next highest
|
||||
power-of-two, regardless of the last value seen.
|
||||
.. versionchanged:: 3.13
|
||||
Prior versions would use the last seen value instead of the highest value.
|
||||
|
||||
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
|
||||
.. versionadded:: 3.7 ``_ignore_``
|
||||
.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_``
|
||||
|
||||
To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can
|
||||
be provided. It will be checked against the actual order of the enumeration
|
||||
|
@ -1447,6 +1451,29 @@ alias::
|
|||
disallowing aliases, the :func:`unique` decorator can be used instead.
|
||||
|
||||
|
||||
MultiValueEnum
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
Supports having more than one value per member::
|
||||
|
||||
>>> class MultiValueEnum(Enum):
|
||||
... def __new__(cls, value, *values):
|
||||
... self = object.__new__(cls)
|
||||
... self._value_ = value
|
||||
... for v in values:
|
||||
... self._add_value_alias_(v)
|
||||
... return self
|
||||
...
|
||||
>>> class DType(MultiValueEnum):
|
||||
... float32 = 'f', 8
|
||||
... double64 = 'd', 9
|
||||
...
|
||||
>>> DType('f')
|
||||
<DType.float32: 'f'>
|
||||
>>> DType(9)
|
||||
<DType.double64: 'd'>
|
||||
|
||||
|
||||
Planet
|
||||
^^^^^^
|
||||
|
||||
|
|
|
@ -235,6 +235,10 @@ Data Types
|
|||
>>> len(Color)
|
||||
3
|
||||
|
||||
.. attribute:: EnumType.__members__
|
||||
|
||||
Returns a mapping of every enum name to its member, including aliases
|
||||
|
||||
.. method:: EnumType.__reversed__(cls)
|
||||
|
||||
Returns each member in *cls* in reverse definition order::
|
||||
|
@ -242,9 +246,19 @@ Data Types
|
|||
>>> list(reversed(Color))
|
||||
[<Color.BLUE: 3>, <Color.GREEN: 2>, <Color.RED: 1>]
|
||||
|
||||
.. method:: EnumType._add_alias_
|
||||
|
||||
Adds a new name as an alias to an existing member. Raises a
|
||||
:exc:`NameError` if the name is already assigned to a different member.
|
||||
|
||||
.. method:: EnumType._add_value_alias_
|
||||
|
||||
Adds a new value as an alias to an existing member. Raises a
|
||||
:exc:`ValueError` if the value is already linked with a different member.
|
||||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
Before 3.11 ``enum`` used ``EnumMeta`` type, which is kept as an alias.
|
||||
Before 3.11 ``EnumType`` was called ``EnumMeta``, which is still available as an alias.
|
||||
|
||||
|
||||
.. class:: Enum
|
||||
|
@ -323,7 +337,7 @@ Data Types
|
|||
>>> PowersOfThree.SECOND.value
|
||||
9
|
||||
|
||||
.. method:: Enum.__init_subclass__(cls, **kwds)
|
||||
.. method:: Enum.__init_subclass__(cls, \**kwds)
|
||||
|
||||
A *classmethod* that is used to further configure subsequent subclasses.
|
||||
By default, does nothing.
|
||||
|
@ -549,7 +563,7 @@ Data Types
|
|||
|
||||
.. method:: __invert__(self):
|
||||
|
||||
Returns all the flags in *type(self)* that are not in self::
|
||||
Returns all the flags in *type(self)* that are not in *self*::
|
||||
|
||||
>>> ~white
|
||||
<Color: 0>
|
||||
|
@ -769,37 +783,41 @@ Supported ``__dunder__`` names
|
|||
:attr:`~EnumType.__members__` is a read-only ordered mapping of ``member_name``:``member``
|
||||
items. It is only available on the class.
|
||||
|
||||
:meth:`~object.__new__`, if specified, must create and return the enum members; it is
|
||||
also a very good idea to set the member's :attr:`!_value_` appropriately. Once
|
||||
all the members are created it is no longer used.
|
||||
:meth:`~object.__new__`, if specified, must create and return the enum members;
|
||||
it is also a very good idea to set the member's :attr:`!_value_` appropriately.
|
||||
Once all the members are created it is no longer used.
|
||||
|
||||
|
||||
Supported ``_sunder_`` names
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
- ``_name_`` -- name of the member
|
||||
- ``_value_`` -- value of the member; can be set / modified in ``__new__``
|
||||
|
||||
- ``_missing_`` -- a lookup function used when a value is not found; may be
|
||||
overridden
|
||||
- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`,
|
||||
that will not be transformed into members, and will be removed from the final
|
||||
class
|
||||
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
|
||||
(class attribute, removed during class creation)
|
||||
- ``_generate_next_value_`` -- used to get an appropriate value for an enum
|
||||
member; may be overridden
|
||||
- :meth:`~EnumType._add_alias_` -- adds a new name as an alias to an existing
|
||||
member.
|
||||
- :meth:`~EnumType._add_value_alias_` -- adds a new value as an alias to an
|
||||
existing member.
|
||||
- :attr:`~Enum._name_` -- name of the member
|
||||
- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__``
|
||||
- :meth:`~Enum._missing_` -- a lookup function used when a value is not found;
|
||||
may be overridden
|
||||
- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a
|
||||
:class:`str`, that will not be transformed into members, and will be removed
|
||||
from the final class
|
||||
- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is
|
||||
consistent (class attribute, removed during class creation)
|
||||
- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for
|
||||
an enum member; may be overridden
|
||||
|
||||
.. note::
|
||||
|
||||
For standard :class:`Enum` classes the next value chosen is the last value seen
|
||||
incremented by one.
|
||||
For standard :class:`Enum` classes the next value chosen is the highest
|
||||
value seen incremented by one.
|
||||
|
||||
For :class:`Flag` classes the next value chosen will be the next highest
|
||||
power-of-two, regardless of the last value seen.
|
||||
power-of-two.
|
||||
|
||||
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
|
||||
.. versionadded:: 3.7 ``_ignore_``
|
||||
.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_``
|
||||
|
||||
---------------
|
||||
|
||||
|
|
203
Lib/enum.py
203
Lib/enum.py
|
@ -4,7 +4,7 @@ from types import MappingProxyType, DynamicClassAttribute
|
|||
|
||||
|
||||
__all__ = [
|
||||
'EnumType', 'EnumMeta',
|
||||
'EnumType', 'EnumMeta', 'EnumDict',
|
||||
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
|
||||
'auto', 'unique', 'property', 'verify', 'member', 'nonmember',
|
||||
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
|
||||
|
@ -313,45 +313,8 @@ class _proto_member:
|
|||
):
|
||||
# no other instances found, record this member in _member_names_
|
||||
enum_class._member_names_.append(member_name)
|
||||
# if necessary, get redirect in place and then add it to _member_map_
|
||||
found_descriptor = None
|
||||
descriptor_type = None
|
||||
class_type = None
|
||||
for base in enum_class.__mro__[1:]:
|
||||
attr = base.__dict__.get(member_name)
|
||||
if attr is not None:
|
||||
if isinstance(attr, (property, DynamicClassAttribute)):
|
||||
found_descriptor = attr
|
||||
class_type = base
|
||||
descriptor_type = 'enum'
|
||||
break
|
||||
elif _is_descriptor(attr):
|
||||
found_descriptor = attr
|
||||
descriptor_type = descriptor_type or 'desc'
|
||||
class_type = class_type or base
|
||||
continue
|
||||
else:
|
||||
descriptor_type = 'attr'
|
||||
class_type = base
|
||||
if found_descriptor:
|
||||
redirect = property()
|
||||
redirect.member = enum_member
|
||||
redirect.__set_name__(enum_class, member_name)
|
||||
if descriptor_type in ('enum','desc'):
|
||||
# earlier descriptor found; copy fget, fset, fdel to this one.
|
||||
redirect.fget = getattr(found_descriptor, 'fget', None)
|
||||
redirect._get = getattr(found_descriptor, '__get__', None)
|
||||
redirect.fset = getattr(found_descriptor, 'fset', None)
|
||||
redirect._set = getattr(found_descriptor, '__set__', None)
|
||||
redirect.fdel = getattr(found_descriptor, 'fdel', None)
|
||||
redirect._del = getattr(found_descriptor, '__delete__', None)
|
||||
redirect._attr_type = descriptor_type
|
||||
redirect._cls_type = class_type
|
||||
setattr(enum_class, member_name, redirect)
|
||||
else:
|
||||
setattr(enum_class, member_name, enum_member)
|
||||
# now add to _member_map_ (even aliases)
|
||||
enum_class._member_map_[member_name] = enum_member
|
||||
|
||||
enum_class._add_member_(member_name, enum_member)
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the value
|
||||
# to the map, and by-value lookups for this value will be
|
||||
|
@ -360,9 +323,10 @@ class _proto_member:
|
|||
except TypeError:
|
||||
# keep track of the value in a list so containment checks are quick
|
||||
enum_class._unhashable_values_.append(value)
|
||||
enum_class._unhashable_values_map_.setdefault(member_name, []).append(value)
|
||||
|
||||
|
||||
class _EnumDict(dict):
|
||||
class EnumDict(dict):
|
||||
"""
|
||||
Track enum member order and ensure member names are not reused.
|
||||
|
||||
|
@ -371,7 +335,7 @@ class _EnumDict(dict):
|
|||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._member_names = {} # use a dict to keep insertion order
|
||||
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7
|
||||
self._last_values = []
|
||||
self._ignore = []
|
||||
self._auto_called = False
|
||||
|
@ -393,6 +357,7 @@ class _EnumDict(dict):
|
|||
'_order_',
|
||||
'_generate_next_value_', '_numeric_repr_', '_missing_', '_ignore_',
|
||||
'_iter_member_', '_iter_member_by_value_', '_iter_member_by_def_',
|
||||
'_add_alias_', '_add_value_alias_',
|
||||
):
|
||||
raise ValueError(
|
||||
'_sunder_ names, such as %r, are reserved for future Enum use'
|
||||
|
@ -468,6 +433,10 @@ class _EnumDict(dict):
|
|||
self._last_values.append(value)
|
||||
super().__setitem__(key, value)
|
||||
|
||||
@property
|
||||
def member_names(self):
|
||||
return list(self._member_names)
|
||||
|
||||
def update(self, members, **more_members):
|
||||
try:
|
||||
for name in members.keys():
|
||||
|
@ -478,6 +447,8 @@ class _EnumDict(dict):
|
|||
for name, value in more_members.items():
|
||||
self[name] = value
|
||||
|
||||
_EnumDict = EnumDict # keep private name for backwards compatibility
|
||||
|
||||
|
||||
class EnumType(type):
|
||||
"""
|
||||
|
@ -489,7 +460,7 @@ class EnumType(type):
|
|||
# check that previous enum members do not exist
|
||||
metacls._check_for_existing_members_(cls, bases)
|
||||
# create the namespace dict
|
||||
enum_dict = _EnumDict()
|
||||
enum_dict = EnumDict()
|
||||
enum_dict._cls_name = cls
|
||||
# inherit previous flags and _generate_next_value_ function
|
||||
member_type, first_enum = metacls._get_mixins_(cls, bases)
|
||||
|
@ -552,6 +523,7 @@ class EnumType(type):
|
|||
classdict['_member_map_'] = {}
|
||||
classdict['_value2member_map_'] = {}
|
||||
classdict['_unhashable_values_'] = []
|
||||
classdict['_unhashable_values_map_'] = {}
|
||||
classdict['_member_type_'] = member_type
|
||||
# now set the __repr__ for the value
|
||||
classdict['_value_repr_'] = metacls._find_data_repr_(cls, bases)
|
||||
|
@ -754,7 +726,10 @@ class EnumType(type):
|
|||
"""
|
||||
if isinstance(value, cls):
|
||||
return True
|
||||
return value in cls._value2member_map_ or value in cls._unhashable_values_
|
||||
try:
|
||||
return value in cls._value2member_map_
|
||||
except TypeError:
|
||||
return value in cls._unhashable_values_
|
||||
|
||||
def __delattr__(cls, attr):
|
||||
# nicer error message when someone tries to delete an attribute
|
||||
|
@ -1050,7 +1025,57 @@ class EnumType(type):
|
|||
else:
|
||||
use_args = True
|
||||
return __new__, save_new, use_args
|
||||
EnumMeta = EnumType
|
||||
|
||||
def _add_member_(cls, name, member):
|
||||
# _value_ structures are not updated
|
||||
if name in cls._member_map_:
|
||||
if cls._member_map_[name] is not member:
|
||||
raise NameError('%r is already bound: %r' % (name, cls._member_map_[name]))
|
||||
return
|
||||
#
|
||||
# if necessary, get redirect in place and then add it to _member_map_
|
||||
found_descriptor = None
|
||||
descriptor_type = None
|
||||
class_type = None
|
||||
for base in cls.__mro__[1:]:
|
||||
attr = base.__dict__.get(name)
|
||||
if attr is not None:
|
||||
if isinstance(attr, (property, DynamicClassAttribute)):
|
||||
found_descriptor = attr
|
||||
class_type = base
|
||||
descriptor_type = 'enum'
|
||||
break
|
||||
elif _is_descriptor(attr):
|
||||
found_descriptor = attr
|
||||
descriptor_type = descriptor_type or 'desc'
|
||||
class_type = class_type or base
|
||||
continue
|
||||
else:
|
||||
descriptor_type = 'attr'
|
||||
class_type = base
|
||||
if found_descriptor:
|
||||
redirect = property()
|
||||
redirect.member = member
|
||||
redirect.__set_name__(cls, name)
|
||||
if descriptor_type in ('enum', 'desc'):
|
||||
# earlier descriptor found; copy fget, fset, fdel to this one.
|
||||
redirect.fget = getattr(found_descriptor, 'fget', None)
|
||||
redirect._get = getattr(found_descriptor, '__get__', None)
|
||||
redirect.fset = getattr(found_descriptor, 'fset', None)
|
||||
redirect._set = getattr(found_descriptor, '__set__', None)
|
||||
redirect.fdel = getattr(found_descriptor, 'fdel', None)
|
||||
redirect._del = getattr(found_descriptor, '__delete__', None)
|
||||
redirect._attr_type = descriptor_type
|
||||
redirect._cls_type = class_type
|
||||
setattr(cls, name, redirect)
|
||||
else:
|
||||
setattr(cls, name, member)
|
||||
# now add to _member_map_ (even aliases)
|
||||
cls._member_map_[name] = member
|
||||
#
|
||||
cls._member_map_[name] = member
|
||||
|
||||
EnumMeta = EnumType # keep EnumMeta name for backwards compatibility
|
||||
|
||||
|
||||
class Enum(metaclass=EnumType):
|
||||
|
@ -1116,9 +1141,9 @@ class Enum(metaclass=EnumType):
|
|||
pass
|
||||
except TypeError:
|
||||
# not there, now do long search -- O(n) behavior
|
||||
for member in cls._member_map_.values():
|
||||
if member._value_ == value:
|
||||
return member
|
||||
for name, values in cls._unhashable_values_map_.items():
|
||||
if value in values:
|
||||
return cls[name]
|
||||
# still not found -- verify that members exist, in-case somebody got here mistakenly
|
||||
# (such as via super when trying to override __new__)
|
||||
if not cls._member_map_:
|
||||
|
@ -1159,6 +1184,33 @@ class Enum(metaclass=EnumType):
|
|||
def __init__(self, *args, **kwds):
|
||||
pass
|
||||
|
||||
def _add_alias_(self, name):
|
||||
self.__class__._add_member_(name, self)
|
||||
|
||||
def _add_value_alias_(self, value):
|
||||
cls = self.__class__
|
||||
try:
|
||||
if value in cls._value2member_map_:
|
||||
if cls._value2member_map_[value] is not self:
|
||||
raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value]))
|
||||
return
|
||||
except TypeError:
|
||||
# unhashable value, do long search
|
||||
for m in cls._member_map_.values():
|
||||
if m._value_ == value:
|
||||
if m is not self:
|
||||
raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value]))
|
||||
return
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the value
|
||||
# to the map, and by-value lookups for this value will be
|
||||
# linear.
|
||||
cls._value2member_map_.setdefault(value, self)
|
||||
except TypeError:
|
||||
# keep track of the value in a list so containment checks are quick
|
||||
cls._unhashable_values_.append(value)
|
||||
cls._unhashable_values_map_.setdefault(self.name, []).append(value)
|
||||
|
||||
@staticmethod
|
||||
def _generate_next_value_(name, start, count, last_values):
|
||||
"""
|
||||
|
@ -1671,7 +1723,8 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
body['_member_names_'] = member_names = []
|
||||
body['_member_map_'] = member_map = {}
|
||||
body['_value2member_map_'] = value2member_map = {}
|
||||
body['_unhashable_values_'] = []
|
||||
body['_unhashable_values_'] = unhashable_values = []
|
||||
body['_unhashable_values_map_'] = {}
|
||||
body['_member_type_'] = member_type = etype._member_type_
|
||||
body['_value_repr_'] = etype._value_repr_
|
||||
if issubclass(etype, Flag):
|
||||
|
@ -1718,14 +1771,9 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
for name, value in attrs.items():
|
||||
if isinstance(value, auto) and auto.value is _auto_null:
|
||||
value = gnv(name, 1, len(member_names), gnv_last_values)
|
||||
if value in value2member_map:
|
||||
if value in value2member_map or value in unhashable_values:
|
||||
# an alias to an existing member
|
||||
member = value2member_map[value]
|
||||
redirect = property()
|
||||
redirect.member = member
|
||||
redirect.__set_name__(enum_class, name)
|
||||
setattr(enum_class, name, redirect)
|
||||
member_map[name] = member
|
||||
enum_class(value)._add_alias_(name)
|
||||
else:
|
||||
# create the member
|
||||
if use_args:
|
||||
|
@ -1740,12 +1788,12 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
member._name_ = name
|
||||
member.__objclass__ = enum_class
|
||||
member.__init__(value)
|
||||
redirect = property()
|
||||
redirect.member = member
|
||||
redirect.__set_name__(enum_class, name)
|
||||
setattr(enum_class, name, redirect)
|
||||
member_map[name] = member
|
||||
member._sort_order_ = len(member_names)
|
||||
if name not in ('name', 'value'):
|
||||
setattr(enum_class, name, member)
|
||||
member_map[name] = member
|
||||
else:
|
||||
enum_class._add_member_(name, member)
|
||||
value2member_map[value] = member
|
||||
if _is_single_bit(value):
|
||||
# not a multi-bit alias, record in _member_names_ and _flag_mask_
|
||||
|
@ -1768,14 +1816,13 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
if value.value is _auto_null:
|
||||
value.value = gnv(name, 1, len(member_names), gnv_last_values)
|
||||
value = value.value
|
||||
if value in value2member_map:
|
||||
try:
|
||||
contained = value in value2member_map
|
||||
except TypeError:
|
||||
contained = value in unhashable_values
|
||||
if contained:
|
||||
# an alias to an existing member
|
||||
member = value2member_map[value]
|
||||
redirect = property()
|
||||
redirect.member = member
|
||||
redirect.__set_name__(enum_class, name)
|
||||
setattr(enum_class, name, redirect)
|
||||
member_map[name] = member
|
||||
enum_class(value)._add_alias_(name)
|
||||
else:
|
||||
# create the member
|
||||
if use_args:
|
||||
|
@ -1791,14 +1838,22 @@ def _simple_enum(etype=Enum, *, boundary=None, use_args=None):
|
|||
member.__objclass__ = enum_class
|
||||
member.__init__(value)
|
||||
member._sort_order_ = len(member_names)
|
||||
redirect = property()
|
||||
redirect.member = member
|
||||
redirect.__set_name__(enum_class, name)
|
||||
setattr(enum_class, name, redirect)
|
||||
member_map[name] = member
|
||||
value2member_map[value] = member
|
||||
if name not in ('name', 'value'):
|
||||
setattr(enum_class, name, member)
|
||||
member_map[name] = member
|
||||
else:
|
||||
enum_class._add_member_(name, member)
|
||||
member_names.append(name)
|
||||
gnv_last_values.append(value)
|
||||
try:
|
||||
# This may fail if value is not hashable. We can't add the value
|
||||
# to the map, and by-value lookups for this value will be
|
||||
# linear.
|
||||
enum_class._value2member_map_.setdefault(value, member)
|
||||
except TypeError:
|
||||
# keep track of the value in a list so containment checks are quick
|
||||
enum_class._unhashable_values_.append(value)
|
||||
enum_class._unhashable_values_map_.setdefault(name, []).append(value)
|
||||
if '__new__' in body:
|
||||
enum_class.__new_member__ = enum_class.__new__
|
||||
enum_class.__new__ = Enum.__new__
|
||||
|
|
|
@ -514,6 +514,7 @@ class _EnumTests:
|
|||
self.assertFalse('first' in MainEnum)
|
||||
val = MainEnum.dupe
|
||||
self.assertIn(val, MainEnum)
|
||||
self.assertNotIn(float('nan'), MainEnum)
|
||||
#
|
||||
class OtherEnum(Enum):
|
||||
one = auto()
|
||||
|
@ -3268,6 +3269,65 @@ class TestSpecial(unittest.TestCase):
|
|||
member._value_ = Base(value)
|
||||
return member
|
||||
|
||||
def test_extra_member_creation(self):
|
||||
class IDEnumMeta(EnumMeta):
|
||||
def __new__(metacls, cls, bases, classdict, **kwds):
|
||||
# add new entries to classdict
|
||||
for name in classdict.member_names:
|
||||
classdict[f'{name}_DESC'] = f'-{classdict[name]}'
|
||||
return super().__new__(metacls, cls, bases, classdict, **kwds)
|
||||
class IDEnum(StrEnum, metaclass=IDEnumMeta):
|
||||
pass
|
||||
class MyEnum(IDEnum):
|
||||
ID = 'id'
|
||||
NAME = 'name'
|
||||
self.assertEqual(list(MyEnum), [MyEnum.ID, MyEnum.NAME, MyEnum.ID_DESC, MyEnum.NAME_DESC])
|
||||
|
||||
def test_add_alias(self):
|
||||
class mixin:
|
||||
@property
|
||||
def ORG(self):
|
||||
return 'huh'
|
||||
class Color(mixin, Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
Color.RED._add_alias_('ROJO')
|
||||
self.assertIs(Color.RED, Color['ROJO'])
|
||||
self.assertIs(Color.RED, Color.ROJO)
|
||||
Color.BLUE._add_alias_('ORG')
|
||||
self.assertIs(Color.BLUE, Color['ORG'])
|
||||
self.assertIs(Color.BLUE, Color.ORG)
|
||||
self.assertEqual(Color.RED.ORG, 'huh')
|
||||
self.assertEqual(Color.GREEN.ORG, 'huh')
|
||||
self.assertEqual(Color.BLUE.ORG, 'huh')
|
||||
self.assertEqual(Color.ORG.ORG, 'huh')
|
||||
|
||||
def test_add_value_alias_after_creation(self):
|
||||
class Color(Enum):
|
||||
RED = 1
|
||||
GREEN = 2
|
||||
BLUE = 3
|
||||
Color.RED._add_value_alias_(5)
|
||||
self.assertIs(Color.RED, Color(5))
|
||||
|
||||
def test_add_value_alias_during_creation(self):
|
||||
class Types(Enum):
|
||||
Unknown = 0,
|
||||
Source = 1, 'src'
|
||||
NetList = 2, 'nl'
|
||||
def __new__(cls, int_value, *value_aliases):
|
||||
member = object.__new__(cls)
|
||||
member._value_ = int_value
|
||||
for alias in value_aliases:
|
||||
member._add_value_alias_(alias)
|
||||
return member
|
||||
self.assertIs(Types(0), Types.Unknown)
|
||||
self.assertIs(Types(1), Types.Source)
|
||||
self.assertIs(Types('src'), Types.Source)
|
||||
self.assertIs(Types(2), Types.NetList)
|
||||
self.assertIs(Types('nl'), Types.NetList)
|
||||
|
||||
|
||||
class TestOrder(unittest.TestCase):
|
||||
"test usage of the `_order_` attribute"
|
||||
|
@ -4941,12 +5001,14 @@ class TestStdLib(unittest.TestCase):
|
|||
@bltns.property
|
||||
def zeroth(self):
|
||||
return 'zeroed %s' % self.name
|
||||
self.assertTrue(_test_simple_enum(CheckedColor, SimpleColor) is None)
|
||||
_test_simple_enum(CheckedColor, SimpleColor)
|
||||
SimpleColor.MAGENTA._value_ = 9
|
||||
self.assertRaisesRegex(
|
||||
TypeError, "enum mismatch",
|
||||
_test_simple_enum, CheckedColor, SimpleColor,
|
||||
)
|
||||
#
|
||||
#
|
||||
class CheckedMissing(IntFlag, boundary=KEEP):
|
||||
SIXTY_FOUR = 64
|
||||
ONE_TWENTY_EIGHT = 128
|
||||
|
@ -4963,8 +5025,28 @@ class TestStdLib(unittest.TestCase):
|
|||
ALL = 2048 + 128 + 64 + 12
|
||||
M = Missing
|
||||
self.assertEqual(list(CheckedMissing), [M.SIXTY_FOUR, M.ONE_TWENTY_EIGHT, M.TWENTY_FORTY_EIGHT])
|
||||
#
|
||||
_test_simple_enum(CheckedMissing, Missing)
|
||||
#
|
||||
#
|
||||
class CheckedUnhashable(Enum):
|
||||
ONE = dict()
|
||||
TWO = set()
|
||||
name = 'python'
|
||||
self.assertIn(dict(), CheckedUnhashable)
|
||||
self.assertIn('python', CheckedUnhashable)
|
||||
self.assertEqual(CheckedUnhashable.name.value, 'python')
|
||||
self.assertEqual(CheckedUnhashable.name.name, 'name')
|
||||
#
|
||||
@_simple_enum()
|
||||
class Unhashable:
|
||||
ONE = dict()
|
||||
TWO = set()
|
||||
name = 'python'
|
||||
self.assertIn(dict(), Unhashable)
|
||||
self.assertIn('python', Unhashable)
|
||||
self.assertEqual(Unhashable.name.value, 'python')
|
||||
self.assertEqual(Unhashable.name.name, 'name')
|
||||
_test_simple_enum(Unhashable, Unhashable)
|
||||
|
||||
|
||||
class MiscTestCase(unittest.TestCase):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
[Enum] Make ``EnumDict``, ``EnumDict.member_names``,
|
||||
``EnumType._add_alias_`` and ``EnumType._add_value_alias_`` public.
|
Loading…
Reference in New Issue