Skip to content

Commit e732a33

Browse files
committed
[Enum] Make some private attributes public.
- ``_EnumDict`` --> ``EnumDict`` - ``EnumDict._member_names`` --> ``EnumDict.member_names`` - ``EnumType._add_alias_`` - ``EnumType._add_value_alias_``
1 parent 9e56eed commit e732a33

File tree

5 files changed

+217
-90
lines changed

5 files changed

+217
-90
lines changed

Doc/howto/enum.rst

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -868,16 +868,16 @@ Others
868868
While :class:`IntEnum` is part of the :mod:`enum` module, it would be very
869869
simple to implement independently::
870870

871-
class IntEnum(int, Enum):
871+
class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum
872872
pass
873873

874874
This demonstrates how similar derived enumerations can be defined; for example
875875
a :class:`FloatEnum` that mixes in :class:`float` instead of :class:`int`.
876876

877877
Some rules:
878878

879-
1. When subclassing :class:`Enum`, mix-in types must appear before
880-
:class:`Enum` itself in the sequence of bases, as in the :class:`IntEnum`
879+
1. When subclassing :class:`Enum`, mix-in types must appear before the
880+
:class:`Enum` class itself in the sequence of bases, as in the :class:`IntEnum`
881881
example above.
882882
2. Mix-in types must be subclassable. For example, :class:`bool` and
883883
:class:`range` are not subclassable and will throw an error during Enum
@@ -961,30 +961,36 @@ all the members are created it is no longer used.
961961
Supported ``_sunder_`` names
962962
""""""""""""""""""""""""""""
963963

964-
- ``_name_`` -- name of the member
965-
- ``_value_`` -- value of the member; can be set / modified in ``__new__``
966-
967-
- ``_missing_`` -- a lookup function used when a value is not found; may be
968-
overridden
969-
- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`,
970-
that will not be transformed into members, and will be removed from the final
971-
class
972-
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
973-
(class attribute, removed during class creation)
974-
- ``_generate_next_value_`` -- used by the `Functional API`_ and by
975-
:class:`auto` to get an appropriate value for an enum member; may be
976-
overridden
977-
978-
.. note::
979-
980-
For standard :class:`Enum` classes the next value chosen is the last value seen
981-
incremented by one.
982-
983-
For :class:`Flag` classes the next value chosen will be the next highest
984-
power-of-two, regardless of the last value seen.
964+
- :meth:`~EnumType._add_alias_` -- adds a new name as an alias to an existing
965+
member.
966+
- :meth:`~EnumType._add_value_alias_` -- adds a new value as an alias to an
967+
existing member.
968+
- :attr:`~Enum._name_` -- name of the member
969+
- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__``
970+
- :meth:`~Enum._missing_` -- a lookup function used when a value is not found;
971+
may be overridden
972+
- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a
973+
:class:`str`, that will not be transformed into members, and will be removed
974+
from the final class
975+
- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is
976+
consistent (class attribute, removed during class creation)
977+
- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for
978+
an enum member; may be overridden
979+
980+
.. note::
981+
982+
For standard :class:`Enum` classes the next value chosen is the highest
983+
value seen incremented by one.
984+
985+
For :class:`Flag` classes the next value chosen will be the next highest
986+
power-of-two.
987+
988+
.. versionchanged:: 3.13
989+
Prior versions would use the last seen value instead of the highest value.
985990

986991
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
987992
.. versionadded:: 3.7 ``_ignore_``
993+
.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_``
988994

989995
To help keep Python 2 / Python 3 code in sync an :attr:`_order_` attribute can
990996
be provided. It will be checked against the actual order of the enumeration

Doc/library/enum.rst

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -235,16 +235,30 @@ Data Types
235235
>>> len(Color)
236236
3
237237

238+
.. attribute: EnumType.__members__
239+
240+
Returns a mapping of every enum name to its member, including aliases
241+
238242
.. method:: EnumType.__reversed__(cls)
239243

240244
Returns each member in *cls* in reverse definition order::
241245

242246
>>> list(reversed(Color))
243247
[<Color.BLUE: 3>, <Color.GREEN: 2>, <Color.RED: 1>]
244248

249+
.. method: EnumType._add_alias_
250+
251+
Adds a new name as an alias to an existing member. Raises a
252+
:exc:`NameError` if the name is already assigned to a different member.
253+
254+
.. method: EnumType._add_value_alias_
255+
256+
Adds a new value as an alias to an existing member. Raises a
257+
:exc:`ValueError` if the value is already linked with a different member.
258+
245259
.. versionadded:: 3.11
246260

247-
Before 3.11 ``enum`` used ``EnumMeta`` type, which is kept as an alias.
261+
Before 3.11 ``EnumType`` was called ``EnumMeta``, which is still available as an alias.
248262

249263

250264
.. class:: Enum
@@ -323,7 +337,7 @@ Data Types
323337
>>> PowersOfThree.SECOND.value
324338
9
325339

326-
.. method:: Enum.__init_subclass__(cls, **kwds)
340+
.. method:: Enum.__init_subclass__(cls, \**kwds)
327341

328342
A *classmethod* that is used to further configure subsequent subclasses.
329343
By default, does nothing.
@@ -549,7 +563,7 @@ Data Types
549563

550564
.. method:: __invert__(self):
551565

552-
Returns all the flags in *type(self)* that are not in self::
566+
Returns all the flags in *type(self)* that are not in *self*::
553567

554568
>>> ~white
555569
<Color: 0>
@@ -769,37 +783,41 @@ Supported ``__dunder__`` names
769783
:attr:`~EnumType.__members__` is a read-only ordered mapping of ``member_name``:``member``
770784
items. It is only available on the class.
771785

772-
:meth:`~object.__new__`, if specified, must create and return the enum members; it is
773-
also a very good idea to set the member's :attr:`!_value_` appropriately. Once
774-
all the members are created it is no longer used.
786+
:meth:`~object.__new__`, if specified, must create and return the enum members;
787+
it is also a very good idea to set the member's :attr:`!_value_` appropriately.
788+
Once all the members are created it is no longer used.
775789

776790

777791
Supported ``_sunder_`` names
778792
""""""""""""""""""""""""""""
779793

780-
- ``_name_`` -- name of the member
781-
- ``_value_`` -- value of the member; can be set / modified in ``__new__``
782-
783-
- ``_missing_`` -- a lookup function used when a value is not found; may be
784-
overridden
785-
- ``_ignore_`` -- a list of names, either as a :class:`list` or a :class:`str`,
786-
that will not be transformed into members, and will be removed from the final
787-
class
788-
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
789-
(class attribute, removed during class creation)
790-
- ``_generate_next_value_`` -- used to get an appropriate value for an enum
791-
member; may be overridden
794+
- :meth:`~EnumType._add_alias_` -- adds a new name as an alias to an existing
795+
member.
796+
- :meth:`~EnumType._add_value_alias_` -- adds a new value as an alias to an
797+
existing member.
798+
- :attr:`~Enum._name_` -- name of the member
799+
- :attr:`~Enum._value_` -- value of the member; can be set in ``__new__``
800+
- :meth:`~Enum._missing_` -- a lookup function used when a value is not found;
801+
may be overridden
802+
- :attr:`~Enum._ignore_` -- a list of names, either as a :class:`list` or a
803+
:class:`str`, that will not be transformed into members, and will be removed
804+
from the final class
805+
- :attr:`~Enum._order_` -- used in Python 2/3 code to ensure member order is
806+
consistent (class attribute, removed during class creation)
807+
- :meth:`~Enum._generate_next_value_` -- used to get an appropriate value for
808+
an enum member; may be overridden
792809

793810
.. note::
794811

795-
For standard :class:`Enum` classes the next value chosen is the last value seen
796-
incremented by one.
812+
For standard :class:`Enum` classes the next value chosen is the highest
813+
value seen incremented by one.
797814

798815
For :class:`Flag` classes the next value chosen will be the next highest
799-
power-of-two, regardless of the last value seen.
816+
power-of-two.
800817

801818
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
802819
.. versionadded:: 3.7 ``_ignore_``
820+
.. versionadded:: 3.13 ``_add_alias_``, ``_add_value_alias_``
803821

804822
---------------
805823

Lib/enum.py

Lines changed: 87 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55

66
__all__ = [
7-
'EnumType', 'EnumMeta',
7+
'EnumType', 'EnumMeta', 'EnumDict',
88
'Enum', 'IntEnum', 'StrEnum', 'Flag', 'IntFlag', 'ReprEnum',
99
'auto', 'unique', 'property', 'verify', 'member', 'nonmember',
1010
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
@@ -313,45 +313,8 @@ def __set_name__(self, enum_class, member_name):
313313
):
314314
# no other instances found, record this member in _member_names_
315315
enum_class._member_names_.append(member_name)
316-
# if necessary, get redirect in place and then add it to _member_map_
317-
found_descriptor = None
318-
descriptor_type = None
319-
class_type = None
320-
for base in enum_class.__mro__[1:]:
321-
attr = base.__dict__.get(member_name)
322-
if attr is not None:
323-
if isinstance(attr, (property, DynamicClassAttribute)):
324-
found_descriptor = attr
325-
class_type = base
326-
descriptor_type = 'enum'
327-
break
328-
elif _is_descriptor(attr):
329-
found_descriptor = attr
330-
descriptor_type = descriptor_type or 'desc'
331-
class_type = class_type or base
332-
continue
333-
else:
334-
descriptor_type = 'attr'
335-
class_type = base
336-
if found_descriptor:
337-
redirect = property()
338-
redirect.member = enum_member
339-
redirect.__set_name__(enum_class, member_name)
340-
if descriptor_type in ('enum','desc'):
341-
# earlier descriptor found; copy fget, fset, fdel to this one.
342-
redirect.fget = getattr(found_descriptor, 'fget', None)
343-
redirect._get = getattr(found_descriptor, '__get__', None)
344-
redirect.fset = getattr(found_descriptor, 'fset', None)
345-
redirect._set = getattr(found_descriptor, '__set__', None)
346-
redirect.fdel = getattr(found_descriptor, 'fdel', None)
347-
redirect._del = getattr(found_descriptor, '__delete__', None)
348-
redirect._attr_type = descriptor_type
349-
redirect._cls_type = class_type
350-
setattr(enum_class, member_name, redirect)
351-
else:
352-
setattr(enum_class, member_name, enum_member)
353-
# now add to _member_map_ (even aliases)
354-
enum_class._member_map_[member_name] = enum_member
316+
317+
enum_class._add_member_(enum_member, member_name)
355318
try:
356319
# This may fail if value is not hashable. We can't add the value
357320
# to the map, and by-value lookups for this value will be
@@ -362,7 +325,7 @@ def __set_name__(self, enum_class, member_name):
362325
enum_class._unhashable_values_.append(value)
363326

364327

365-
class _EnumDict(dict):
328+
class EnumDict(dict):
366329
"""
367330
Track enum member order and ensure member names are not reused.
368331
@@ -371,7 +334,7 @@ class _EnumDict(dict):
371334
"""
372335
def __init__(self):
373336
super().__init__()
374-
self._member_names = {} # use a dict to keep insertion order
337+
self._member_names = {} # use a dict -- faster look-up than a list, and keeps insertion order since 3.7 (?)
375338
self._last_values = []
376339
self._ignore = []
377340
self._auto_called = False
@@ -468,6 +431,10 @@ def __setitem__(self, key, value):
468431
self._last_values.append(value)
469432
super().__setitem__(key, value)
470433

434+
@property
435+
def member_names(self):
436+
return list(self._member_names)
437+
471438
def update(self, members, **more_members):
472439
try:
473440
for name in members.keys():
@@ -478,6 +445,8 @@ def update(self, members, **more_members):
478445
for name, value in more_members.items():
479446
self[name] = value
480447

448+
_EnumDict = EnumDict # keep private name for backwards compatibility
449+
481450

482451
class EnumType(type):
483452
"""
@@ -489,7 +458,7 @@ def __prepare__(metacls, cls, bases, **kwds):
489458
# check that previous enum members do not exist
490459
metacls._check_for_existing_members_(cls, bases)
491460
# create the namespace dict
492-
enum_dict = _EnumDict()
461+
enum_dict = EnumDict()
493462
enum_dict._cls_name = cls
494463
# inherit previous flags and _generate_next_value_ function
495464
member_type, first_enum = metacls._get_mixins_(cls, bases)
@@ -1046,7 +1015,80 @@ def _find_new_(mcls, classdict, member_type, first_enum):
10461015
else:
10471016
use_args = True
10481017
return __new__, save_new, use_args
1049-
EnumMeta = EnumType
1018+
1019+
def _add_alias_(cls, member, name):
1020+
if name in cls._member_map_:
1021+
if cls._member_map_[name] is not member:
1022+
raise NameError('%r is already bound: %r' % (name, cls._member_map_[name]))
1023+
return
1024+
#
1025+
# if necessary, get redirect in place and then add it to _member_map_
1026+
found_descriptor = None
1027+
descriptor_type = None
1028+
class_type = None
1029+
for base in cls.__mro__[1:]:
1030+
attr = base.__dict__.get(name)
1031+
if attr is not None:
1032+
if isinstance(attr, (property, DynamicClassAttribute)):
1033+
found_descriptor = attr
1034+
class_type = base
1035+
descriptor_type = 'enum'
1036+
break
1037+
elif _is_descriptor(attr):
1038+
found_descriptor = attr
1039+
descriptor_type = descriptor_type or 'desc'
1040+
class_type = class_type or base
1041+
continue
1042+
else:
1043+
descriptor_type = 'attr'
1044+
class_type = base
1045+
if found_descriptor:
1046+
redirect = property()
1047+
redirect.member = member
1048+
redirect.__set_name__(cls, name)
1049+
if descriptor_type in ('enum','desc'):
1050+
# earlier descriptor found; copy fget, fset, fdel to this one.
1051+
redirect.fget = getattr(found_descriptor, 'fget', None)
1052+
redirect._get = getattr(found_descriptor, '__get__', None)
1053+
redirect.fset = getattr(found_descriptor, 'fset', None)
1054+
redirect._set = getattr(found_descriptor, '__set__', None)
1055+
redirect.fdel = getattr(found_descriptor, 'fdel', None)
1056+
redirect._del = getattr(found_descriptor, '__delete__', None)
1057+
redirect._attr_type = descriptor_type
1058+
redirect._cls_type = class_type
1059+
setattr(cls, name, redirect)
1060+
else:
1061+
setattr(cls, name, member)
1062+
# now add to _member_map_ (even aliases)
1063+
cls._member_map_[name] = member
1064+
#
1065+
cls._member_map_[name] = member
1066+
_add_member_ = _add_alias_ # use _add_member_ internally
1067+
1068+
def _add_value_alias_(cls, member, value):
1069+
try:
1070+
if value in cls._value2member_map_:
1071+
if cls._value2member_map_[value] is not member:
1072+
raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value]))
1073+
return
1074+
except TypeError:
1075+
# unhashable value, do long search
1076+
for m in cls._member_map_.values():
1077+
if m._value_ == value:
1078+
if m is not member:
1079+
raise ValueError('%r is already bound: %r' % (value, cls._value2member_map_[value]))
1080+
return
1081+
try:
1082+
# This may fail if value is not hashable. We can't add the value
1083+
# to the map, and by-value lookups for this value will be
1084+
# linear.
1085+
cls._value2member_map_.setdefault(value, member)
1086+
except TypeError:
1087+
# keep track of the value in a list so containment checks are quick
1088+
cls._unhashable_values_.append(value)
1089+
1090+
1091+
EnumMeta = EnumType # keep EnumMeta name for backwards compatibility
10501092

10511093

10521094
class Enum(metaclass=EnumType):
@@ -1113,7 +1155,7 @@ def __new__(cls, value):
11131155
except TypeError:
11141156
# not there, now do long search -- O(n) behavior
11151157
for member in cls._member_map_.values():
1116-
if member._value_ == value:
1158+
if member._value_ is value or member._value_ == value:
11171159
return member
11181160
# still not found -- verify that members exist, in-case somebody got here mistakenly
11191161
# (such as via super when trying to override __new__)

0 commit comments

Comments
 (0)