Skip to content

Commit c16595e

Browse files
committed
issue23591: add auto() for auto-generating Enum member values
1 parent 944368e commit c16595e

File tree

3 files changed

+195
-31
lines changed

3 files changed

+195
-31
lines changed

Doc/library/enum.rst

+83-16
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ Module Contents
2525

2626
This module defines four enumeration classes that can be used to define unique
2727
sets of names and values: :class:`Enum`, :class:`IntEnum`, and
28-
:class:`IntFlags`. It also defines one decorator, :func:`unique`.
28+
:class:`IntFlags`. It also defines one decorator, :func:`unique`, and one
29+
helper, :class:`auto`.
2930

3031
.. class:: Enum
3132

@@ -52,7 +53,11 @@ sets of names and values: :class:`Enum`, :class:`IntEnum`, and
5253

5354
Enum class decorator that ensures only one name is bound to any one value.
5455

55-
.. versionadded:: 3.6 ``Flag``, ``IntFlag``
56+
.. class:: auto
57+
58+
Instances are replaced with an appropriate value for Enum members.
59+
60+
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
5661

5762

5863
Creating an Enum
@@ -70,6 +75,13 @@ follows::
7075
... blue = 3
7176
...
7277

78+
.. note:: Enum member values
79+
80+
Member values can be anything: :class:`int`, :class:`str`, etc.. If
81+
the exact value is unimportant you may use :class:`auto` instances and an
82+
appropriate value will be chosen for you. Care must be taken if you mix
83+
:class:`auto` with other values.
84+
7385
.. note:: Nomenclature
7486

7587
- The class :class:`Color` is an *enumeration* (or *enum*)
@@ -225,6 +237,42 @@ found :exc:`ValueError` is raised with the details::
225237
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
226238

227239

240+
Using automatic values
241+
----------------------
242+
243+
If the exact value is unimportant you can use :class:`auto`::
244+
245+
>>> from enum import Enum, auto
246+
>>> class Color(Enum):
247+
... red = auto()
248+
... blue = auto()
249+
... green = auto()
250+
...
251+
>>> list(Color)
252+
[<Color.red: 1>, <Color.blue: 2>, <Color.green: 3>]
253+
254+
The values are chosen by :func:`_generate_next_value_`, which can be
255+
overridden::
256+
257+
>>> class AutoName(Enum):
258+
... def _generate_next_value_(name, start, count, last_values):
259+
... return name
260+
...
261+
>>> class Ordinal(AutoName):
262+
... north = auto()
263+
... south = auto()
264+
... east = auto()
265+
... west = auto()
266+
...
267+
>>> list(Ordinal)
268+
[<Ordinal.north: 'north'>, <Ordinal.south: 'south'>, <Ordinal.east: 'east'>, <Ordinal.west: 'west'>]
269+
270+
.. note::
271+
272+
The goal of the default :meth:`_generate_next_value_` methods is to provide
273+
the next :class:`int` in sequence with the last :class:`int` provided, but
274+
the way it does this is an implementation detail and may change.
275+
228276
Iteration
229277
---------
230278

@@ -597,7 +645,9 @@ Flag
597645
The last variation is :class:`Flag`. Like :class:`IntFlag`, :class:`Flag`
598646
members can be combined using the bitwise operators (&, \|, ^, ~). Unlike
599647
:class:`IntFlag`, they cannot be combined with, nor compared against, any
600-
other :class:`Flag` enumeration, nor :class:`int`.
648+
other :class:`Flag` enumeration, nor :class:`int`. While it is possible to
649+
specify the values directly it is recommended to use :class:`auto` as the
650+
value and let :class:`Flag` select an appropriate value.
601651

602652
.. versionadded:: 3.6
603653

@@ -606,9 +656,9 @@ flags being set, the boolean evaluation is :data:`False`::
606656

607657
>>> from enum import Flag
608658
>>> class Color(Flag):
609-
... red = 1
610-
... blue = 2
611-
... green = 4
659+
... red = auto()
660+
... blue = auto()
661+
... green = auto()
612662
...
613663
>>> Color.red & Color.green
614664
<Color.0: 0>
@@ -619,21 +669,20 @@ Individual flags should have values that are powers of two (1, 2, 4, 8, ...),
619669
while combinations of flags won't::
620670

621671
>>> class Color(Flag):
622-
... red = 1
623-
... blue = 2
624-
... green = 4
625-
... white = 7
626-
... # or
627-
... # white = red | blue | green
672+
... red = auto()
673+
... blue = auto()
674+
... green = auto()
675+
... white = red | blue | green
676+
...
628677

629678
Giving a name to the "no flags set" condition does not change its boolean
630679
value::
631680

632681
>>> class Color(Flag):
633682
... black = 0
634-
... red = 1
635-
... blue = 2
636-
... green = 4
683+
... red = auto()
684+
... blue = auto()
685+
... green = auto()
637686
...
638687
>>> Color.black
639688
<Color.black: 0>
@@ -700,6 +749,7 @@ Omitting values
700749
In many use-cases one doesn't care what the actual value of an enumeration
701750
is. There are several ways to define this type of simple enumeration:
702751

752+
- use instances of :class:`auto` for the value
703753
- use instances of :class:`object` as the value
704754
- use a descriptive string as the value
705755
- use a tuple as the value and a custom :meth:`__new__` to replace the
@@ -718,6 +768,20 @@ the (unimportant) value::
718768
...
719769

720770

771+
Using :class:`auto`
772+
"""""""""""""""""""
773+
774+
Using :class:`object` would look like::
775+
776+
>>> class Color(NoValue):
777+
... red = auto()
778+
... blue = auto()
779+
... green = auto()
780+
...
781+
>>> Color.green
782+
<Color.green>
783+
784+
721785
Using :class:`object`
722786
"""""""""""""""""""""
723787

@@ -930,8 +994,11 @@ Supported ``_sunder_`` names
930994
overridden
931995
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
932996
(class attribute, removed during class creation)
997+
- ``_generate_next_value_`` -- used by the `Functional API`_ and by
998+
:class:`auto` to get an appropriate value for an enum member; may be
999+
overridden
9331000

934-
.. versionadded:: 3.6 ``_missing_``, ``_order_``
1001+
.. versionadded:: 3.6 ``_missing_``, ``_order_``, ``_generate_next_value_``
9351002

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

Lib/enum.py

+37-13
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@
1010
from collections import OrderedDict
1111

1212

13-
__all__ = ['EnumMeta', 'Enum', 'IntEnum', 'Flag', 'IntFlag', 'unique']
13+
__all__ = [
14+
'EnumMeta',
15+
'Enum', 'IntEnum', 'Flag', 'IntFlag',
16+
'auto', 'unique',
17+
]
1418

1519

1620
def _is_descriptor(obj):
@@ -36,14 +40,19 @@ def _is_sunder(name):
3640
name[-2:-1] != '_' and
3741
len(name) > 2)
3842

39-
4043
def _make_class_unpicklable(cls):
4144
"""Make the given class un-picklable."""
4245
def _break_on_call_reduce(self, proto):
4346
raise TypeError('%r cannot be pickled' % self)
4447
cls.__reduce_ex__ = _break_on_call_reduce
4548
cls.__module__ = '<unknown>'
4649

50+
class auto:
51+
"""
52+
Instances are replaced with an appropriate value in Enum class suites.
53+
"""
54+
pass
55+
4756

4857
class _EnumDict(dict):
4958
"""Track enum member order and ensure member names are not reused.
@@ -55,6 +64,7 @@ class _EnumDict(dict):
5564
def __init__(self):
5665
super().__init__()
5766
self._member_names = []
67+
self._last_values = []
5868

5969
def __setitem__(self, key, value):
6070
"""Changes anything not dundered or not a descriptor.
@@ -71,6 +81,8 @@ def __setitem__(self, key, value):
7181
'_generate_next_value_', '_missing_',
7282
):
7383
raise ValueError('_names_ are reserved for future Enum use')
84+
if key == '_generate_next_value_':
85+
setattr(self, '_generate_next_value', value)
7486
elif _is_dunder(key):
7587
if key == '__order__':
7688
key = '_order_'
@@ -81,11 +93,13 @@ def __setitem__(self, key, value):
8193
if key in self:
8294
# enum overwriting a descriptor?
8395
raise TypeError('%r already defined as: %r' % (key, self[key]))
96+
if isinstance(value, auto):
97+
value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])
8498
self._member_names.append(key)
99+
self._last_values.append(value)
85100
super().__setitem__(key, value)
86101

87102

88-
89103
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
90104
# until EnumMeta finishes running the first time the Enum class doesn't exist.
91105
# This is also why there are checks in EnumMeta like `if Enum is not None`
@@ -366,10 +380,11 @@ def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=No
366380
names = names.replace(',', ' ').split()
367381
if isinstance(names, (tuple, list)) and isinstance(names[0], str):
368382
original_names, names = names, []
369-
last_value = None
383+
last_values = []
370384
for count, name in enumerate(original_names):
371-
last_value = first_enum._generate_next_value_(name, start, count, last_value)
372-
names.append((name, last_value))
385+
value = first_enum._generate_next_value_(name, start, count, last_values[:])
386+
last_values.append(value)
387+
names.append((name, value))
373388

374389
# Here, names is either an iterable of (name, value) or a mapping.
375390
for item in names:
@@ -514,11 +529,15 @@ def __new__(cls, value):
514529
# still not found -- try _missing_ hook
515530
return cls._missing_(value)
516531

517-
@staticmethod
518-
def _generate_next_value_(name, start, count, last_value):
519-
if not count:
532+
def _generate_next_value_(name, start, count, last_values):
533+
for last_value in reversed(last_values):
534+
try:
535+
return last_value + 1
536+
except TypeError:
537+
pass
538+
else:
520539
return start
521-
return last_value + 1
540+
522541
@classmethod
523542
def _missing_(cls, value):
524543
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
@@ -616,8 +635,8 @@ def _reduce_ex_by_name(self, proto):
616635

617636
class Flag(Enum):
618637
"""Support for flags"""
619-
@staticmethod
620-
def _generate_next_value_(name, start, count, last_value):
638+
639+
def _generate_next_value_(name, start, count, last_values):
621640
"""
622641
Generate the next value when not given.
623642
@@ -628,7 +647,12 @@ def _generate_next_value_(name, start, count, last_value):
628647
"""
629648
if not count:
630649
return start if start is not None else 1
631-
high_bit = _high_bit(last_value)
650+
for last_value in reversed(last_values):
651+
try:
652+
high_bit = _high_bit(last_value)
653+
break
654+
except TypeError:
655+
raise TypeError('Invalid Flag value: %r' % last_value) from None
632656
return 2 ** (high_bit+1)
633657

634658
@classmethod

0 commit comments

Comments
 (0)