cpython: aad7443e62be (original) (raw)
Mercurial > cpython
changeset 103609:aad7443e62be
issue23591: add auto() for auto-generating Enum member values [#23591]
Ethan Furman ethan@stoneleaf.us | |
---|---|
date | Sat, 10 Sep 2016 23:36:59 -0700 |
parents | feb1ae9d5381 |
children | d00f15af75ea |
files | Doc/library/enum.rst Lib/enum.py Lib/test/test_enum.py |
diffstat | 3 files changed, 195 insertions(+), 31 deletions(-)[+] [-] Doc/library/enum.rst 99 Lib/enum.py 50 Lib/test/test_enum.py 77 |
line wrap: on
line diff
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -25,7 +25,8 @@ Module Contents
This module defines four enumeration classes that can be used to define unique
sets of names and values: :class:Enum
, :class:IntEnum
, and
-:class:IntFlags
. It also defines one decorator, :func:unique
.
+:class:IntFlags
. It also defines one decorator, :func:unique
, and one
+helper, :class:auto
.
.. class:: Enum
@@ -52,7 +53,11 @@ sets of names and values: :class:Enum
,
Enum class decorator that ensures only one name is bound to any one value.
-.. versionadded:: 3.6 Flag
, IntFlag
+.. class:: auto
+
+
+.. versionadded:: 3.6 Flag
, IntFlag
, auto
Creating an Enum
@@ -70,6 +75,13 @@ follows::
... blue = 3
...
+.. note:: Enum member values
+
- Member values can be anything: :class:
int
, :class:str
, etc.. If - the exact value is unimportant you may use :class:
auto
instances and an - appropriate value will be chosen for you. Care must be taken if you mix
- :class:
auto
with other values.
- The class :class:
Color
is an enumeration (or enum) @@ -225,6 +237,42 @@ found :exc:ValueError
is raised with t ValueError: duplicate values found in <enum 'Mistake'>: four -> three +Using automatic values +---------------------- + +If the exact value is unimportant you can use :class:auto
:: +
- ... red = auto()
- ... blue = auto()
- ... green = auto()
- ...
- [<Color.red: 1>, <Color.blue: 2>, <Color.green: 3>]
+
+The values are chosen by :func:_generate_next_value_
, which can be
+overridden::
+
- ... def generate_next_value(name, start, count, last_values):
- ... return name
- ...
- ... north = auto()
- ... south = auto()
- ... east = auto()
- ... west = auto()
- ...
- [<Ordinal.north: 'north'>, <Ordinal.south: 'south'>, <Ordinal.east: 'east'>, <Ordinal.west: 'west'>]
- The goal of the default :meth:
_generate_next_value_
methods is to provide - the next :class:
int
in sequence with the last :class:int
provided, but - the way it does this is an implementation detail and may change.
+
Iteration
---------
@@ -597,7 +645,9 @@ Flag
The last variation is :class:Flag
. Like :class:IntFlag
, :class:Flag
members can be combined using the bitwise operators (&, |, ^, ~). Unlike
:class:IntFlag
, they cannot be combined with, nor compared against, any
-other :class:Flag
enumeration, nor :class:int
.
+other :class:Flag
enumeration, nor :class:int
. While it is possible to
+specify the values directly it is recommended to use :class:auto
as the
+value and let :class:Flag
select an appropriate value.
.. versionadded:: 3.6
@@ -606,9 +656,9 @@ flags being set, the boolean evaluation
>>> from enum import Flag
>>> class Color(Flag):
- ... red = auto()
- ... blue = auto()
- ... green = auto()
...
Color.red & Color.green <Color.0: 0> @@ -619,21 +669,20 @@ Individual flags should have values that while combinations of flags won't:: class Color(Flag):
Giving a name to the "no flags set" condition does not change its boolean value:: >>> class Color(Flag): ... black = 0
- ... red = auto()
- ... blue = auto()
- ... green = auto()
...
Color.black <Color.black: 0> @@ -700,6 +749,7 @@ Omitting values In many use-cases one doesn't care what the actual value of an enumeration is. There are several ways to define this type of simple enumeration: +- use instances of :class:
auto
for the value
- use instances of :class:
object
as the value - use a descriptive string as the value
- use a tuple as the value and a custom :meth:
__new__
to replace the @@ -718,6 +768,20 @@ the (unimportant) value:: ... +Using :class:auto
+""""""""""""""""""" + +Using :class:object
would look like:: +
+
+
Using :class:object
"""""""""""""""""""""
@@ -930,8 +994,11 @@ Supported _sunder_
names
overridden
_order_
-- used in Python 2/3 code to ensure member order is consistent (class attribute, removed during class creation) +-_generate_next_value_
-- used by theFunctional API
_ and by
- :class:
auto
to get an appropriate value for an enum member; may be - overridden
-.. versionadded:: 3.6
_missing_
,_order_
+.. versionadded:: 3.6_missing_
,_order_
,_generate_next_value_
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
--- a/Lib/enum.py +++ b/Lib/enum.py @@ -10,7 +10,11 @@ except ImportError: from collections import OrderedDict -all = ['EnumMeta', 'Enum', 'IntEnum', 'Flag', 'IntFlag', 'unique'] +all = [
'EnumMeta',[](#l2.9)
'Enum', 'IntEnum', 'Flag', 'IntFlag',[](#l2.10)
'auto', 'unique',[](#l2.11)
][](#l2.12)
def _is_descriptor(obj): @@ -36,7 +40,6 @@ def is_sunder(name): name[-2:-1] != '' and len(name) > 2) - def _make_class_unpicklable(cls): """Make the given class un-picklable.""" def _break_on_call_reduce(self, proto): @@ -44,6 +47,12 @@ def _make_class_unpicklable(cls): cls.reduce_ex = _break_on_call_reduce cls.module = '' +class auto:
+ class _EnumDict(dict): """Track enum member order and ensure member names are not reused. @@ -55,6 +64,7 @@ class _EnumDict(dict): def init(self): super().init() self._member_names = []
self._last_values = [][](#l2.41)
def setitem(self, key, value): """Changes anything not dundered or not a descriptor. @@ -71,6 +81,8 @@ class _EnumDict(dict): 'generate_next_value', 'missing', ): raise ValueError('names are reserved for future Enum use')
if key == '_generate_next_value_':[](#l2.49)
setattr(self, '_generate_next_value', value)[](#l2.50) elif _is_dunder(key):[](#l2.51) if key == '__order__':[](#l2.52) key = '_order_'[](#l2.53)
@@ -81,11 +93,13 @@ class _EnumDict(dict): if key in self: # enum overwriting a descriptor? raise TypeError('%r already defined as: %r' % (key, self[key]))
if isinstance(value, auto):[](#l2.58)
value = self._generate_next_value(key, 1, len(self._member_names), self._last_values[:])[](#l2.59) self._member_names.append(key)[](#l2.60)
self._last_values.append(value)[](#l2.61) super().__setitem__(key, value)[](#l2.62)
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.
This is also why there are checks in EnumMeta like if Enum is not None
@@ -366,10 +380,11 @@ class EnumMeta(type): names = names.replace(',', ' ').split() if isinstance(names, (tuple, list)) and isinstance(names[0], str): original_names, names = names, []
last_value = None[](#l2.73)
last_values = [][](#l2.74) for count, name in enumerate(original_names):[](#l2.75)
last_value = first_enum._generate_next_value_(name, start, count, last_value)[](#l2.76)
names.append((name, last_value))[](#l2.77)
value = first_enum._generate_next_value_(name, start, count, last_values[:])[](#l2.78)
last_values.append(value)[](#l2.79)
names.append((name, value))[](#l2.80)
# Here, names is either an iterable of (name, value) or a mapping. for item in names: @@ -514,11 +529,15 @@ class Enum(metaclass=EnumMeta): # still not found -- try missing hook return cls.missing(value)
- def generate_next_value(name, start, count, last_values):
for last_value in reversed(last_values):[](#l2.92)
try:[](#l2.93)
return last_value + 1[](#l2.94)
except TypeError:[](#l2.95)
pass[](#l2.96)
else:[](#l2.97) return start[](#l2.98)
return last_value + 1[](#l2.99)
+ @classmethod def missing(cls, value): raise ValueError("%r is not a valid %s" % (value, cls.name)) @@ -616,8 +635,8 @@ def _reduce_ex_by_name(self, proto): class Flag(Enum): """Support for flags"""
- def generate_next_value(name, start, count, last_values): """ Generate the next value when not given.
@@ -628,7 +647,12 @@ class Flag(Enum): """ if not count: return start if start is not None else 1
high_bit = _high_bit(last_value)[](#l2.119)
for last_value in reversed(last_values):[](#l2.120)
try:[](#l2.121)
high_bit = _high_bit(last_value)[](#l2.122)
break[](#l2.123)
except TypeError:[](#l2.124)
raise TypeError('Invalid Flag value: %r' % last_value) from None[](#l2.125) return 2 ** (high_bit+1)[](#l2.126)
--- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -3,7 +3,7 @@ import inspect import pydoc import unittest from collections import OrderedDict -from enum import Enum, IntEnum, EnumMeta, Flag, IntFlag, unique +from enum import Enum, IntEnum, EnumMeta, Flag, IntFlag, unique, auto from io import StringIO from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL from test import support @@ -113,6 +113,7 @@ class TestHelpers(unittest.TestCase): '', '', '', '__',): self.assertFalse(enum._is_dunder(s)) +# tests class TestEnum(unittest.TestCase): @@ -1578,6 +1579,61 @@ class TestEnum(unittest.TestCase): self.assertEqual(LabelledList.unprocessed, 1) self.assertEqual(LabelledList(1), LabelledList.unprocessed)
- def test_auto_number(self):
class Color(Enum):[](#l3.25)
red = auto()[](#l3.26)
blue = auto()[](#l3.27)
green = auto()[](#l3.28)
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])[](#l3.30)
self.assertEqual(Color.red.value, 1)[](#l3.31)
self.assertEqual(Color.blue.value, 2)[](#l3.32)
self.assertEqual(Color.green.value, 3)[](#l3.33)
- def test_auto_name(self):
class Color(Enum):[](#l3.36)
def _generate_next_value_(name, start, count, last):[](#l3.37)
return name[](#l3.38)
red = auto()[](#l3.39)
blue = auto()[](#l3.40)
green = auto()[](#l3.41)
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])[](#l3.43)
self.assertEqual(Color.red.value, 'red')[](#l3.44)
self.assertEqual(Color.blue.value, 'blue')[](#l3.45)
self.assertEqual(Color.green.value, 'green')[](#l3.46)
- def test_auto_name_inherit(self):
class AutoNameEnum(Enum):[](#l3.49)
def _generate_next_value_(name, start, count, last):[](#l3.50)
return name[](#l3.51)
class Color(AutoNameEnum):[](#l3.52)
red = auto()[](#l3.53)
blue = auto()[](#l3.54)
green = auto()[](#l3.55)
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])[](#l3.57)
self.assertEqual(Color.red.value, 'red')[](#l3.58)
self.assertEqual(Color.blue.value, 'blue')[](#l3.59)
self.assertEqual(Color.green.value, 'green')[](#l3.60)
- def test_auto_garbage(self):
class Color(Enum):[](#l3.63)
red = 'red'[](#l3.64)
blue = auto()[](#l3.65)
self.assertEqual(Color.blue.value, 1)[](#l3.66)
- def test_auto_garbage_corrected(self):
class Color(Enum):[](#l3.69)
red = 'red'[](#l3.70)
blue = 2[](#l3.71)
green = auto()[](#l3.72)
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])[](#l3.74)
self.assertEqual(Color.red.value, 'red')[](#l3.75)
self.assertEqual(Color.blue.value, 2)[](#l3.76)
self.assertEqual(Color.green.value, 3)[](#l3.77)
+ class TestOrder(unittest.TestCase): @@ -1856,7 +1912,6 @@ class TestFlag(unittest.TestCase): test_pickle_dump_load(self.assertIs, FlagStooges.CURLY|FlagStooges.MOE) test_pickle_dump_load(self.assertIs, FlagStooges) - def test_containment(self): Perm = self.Perm R, W, X = Perm @@ -1877,6 +1932,24 @@ class TestFlag(unittest.TestCase): self.assertFalse(W in RX) self.assertFalse(X in RW)
- def test_auto_number(self):
class Color(Flag):[](#l3.95)
red = auto()[](#l3.96)
blue = auto()[](#l3.97)
green = auto()[](#l3.98)
self.assertEqual(list(Color), [Color.red, Color.blue, Color.green])[](#l3.100)
self.assertEqual(Color.red.value, 1)[](#l3.101)
self.assertEqual(Color.blue.value, 2)[](#l3.102)
self.assertEqual(Color.green.value, 4)[](#l3.103)
- def test_auto_number_garbage(self):
with self.assertRaisesRegex(TypeError, 'Invalid Flag value: .not an int.'):[](#l3.106)
class Color(Flag):[](#l3.107)
red = 'not an int'[](#l3.108)
blue = auto()[](#l3.109)
+ + class TestIntFlag(unittest.TestCase): """Tests of the IntFlags."""