Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
48bc4418
Commit
48bc4418
authored
Sep 10, 2016
by
Ethan Furman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
issue23591: add auto() for auto-generating Enum member values
parent
403ccddb
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
195 additions
and
31 deletions
+195
-31
Doc/library/enum.rst
Doc/library/enum.rst
+83
-16
Lib/enum.py
Lib/enum.py
+37
-13
Lib/test/test_enum.py
Lib/test/test_enum.py
+75
-2
No files found.
Doc/library/enum.rst
View file @
48bc4418
...
@@ -25,7 +25,8 @@ Module Contents
...
@@ -25,7 +25,8 @@ Module Contents
This module defines four enumeration classes that can be used to define unique
This module defines four enumeration classes that can be used to define unique
sets of names and values: :class:`Enum`, :class:`IntEnum`, and
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
.. class:: Enum
...
@@ -52,7 +53,11 @@ sets of names and values: :class:`Enum`, :class:`IntEnum`, and
...
@@ -52,7 +53,11 @@ sets of names and values: :class:`Enum`, :class:`IntEnum`, and
Enum class decorator that ensures only one name is bound to any one value.
Enum class decorator that ensures only one name is bound to any one value.
.. versionadded:: 3.6 ``Flag``, ``IntFlag``
.. class:: auto
Instances are replaced with an appropriate value for Enum members.
.. versionadded:: 3.6 ``Flag``, ``IntFlag``, ``auto``
Creating an Enum
Creating an Enum
...
@@ -70,6 +75,13 @@ follows::
...
@@ -70,6 +75,13 @@ follows::
... blue = 3
... 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.
.. note:: Nomenclature
.. note:: Nomenclature
- The class :class:`Color` is an *enumeration* (or *enum*)
- The class :class:`Color` is an *enumeration* (or *enum*)
...
@@ -225,6 +237,42 @@ found :exc:`ValueError` is raised with the details::
...
@@ -225,6 +237,42 @@ found :exc:`ValueError` is raised with the details::
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
Using automatic values
----------------------
If the exact value is unimportant you can use :class:`auto`::
>>> from enum import Enum, auto
>>> class Color(Enum):
... red = auto()
... blue = auto()
... green = auto()
...
>>> list(Color)
[<Color.red: 1>, <Color.blue: 2>, <Color.green: 3>]
The values are chosen by :func:`_generate_next_value_`, which can be
overridden::
>>> class AutoName(Enum):
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... north = auto()
... south = auto()
... east = auto()
... west = auto()
...
>>> list(Ordinal)
[<Ordinal.north: 'north'>, <Ordinal.south: 'south'>, <Ordinal.east: 'east'>, <Ordinal.west: 'west'>]
.. note::
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
Iteration
---------
---------
...
@@ -597,7 +645,9 @@ Flag
...
@@ -597,7 +645,9 @@ Flag
The last variation is :class:`Flag`. Like :class:`IntFlag`, :class:`Flag`
The last variation is :class:`Flag`. Like :class:`IntFlag`, :class:`Flag`
members can be combined using the bitwise operators (&, \|, ^, ~). Unlike
members can be combined using the bitwise operators (&, \|, ^, ~). Unlike
:class:`IntFlag`, they cannot be combined with, nor compared against, any
: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
.. versionadded:: 3.6
...
@@ -606,9 +656,9 @@ flags being set, the boolean evaluation is :data:`False`::
...
@@ -606,9 +656,9 @@ flags being set, the boolean evaluation is :data:`False`::
>>> from enum import Flag
>>> from enum import Flag
>>> class Color(Flag):
>>> class Color(Flag):
... red =
1
... red =
auto()
... blue =
2
... blue =
auto()
... green =
4
... green =
auto()
...
...
>>> Color.red & Color.green
>>> Color.red & Color.green
<Color.0: 0>
<Color.0: 0>
...
@@ -619,21 +669,20 @@ Individual flags should have values that are powers of two (1, 2, 4, 8, ...),
...
@@ -619,21 +669,20 @@ Individual flags should have values that are powers of two (1, 2, 4, 8, ...),
while combinations of flags won't::
while combinations of flags won't::
>>> class Color(Flag):
>>> class Color(Flag):
... red = 1
... red = auto()
... blue = 2
... blue = auto()
... green = 4
... green = auto()
... white = 7
... white = red | blue | green
... # or
...
... # white = red | blue | green
Giving a name to the "no flags set" condition does not change its boolean
Giving a name to the "no flags set" condition does not change its boolean
value::
value::
>>> class Color(Flag):
>>> class Color(Flag):
... black = 0
... black = 0
... red =
1
... red =
auto()
... blue =
2
... blue =
auto()
... green =
4
... green =
auto()
...
...
>>> Color.black
>>> Color.black
<Color.black: 0>
<Color.black: 0>
...
@@ -700,6 +749,7 @@ Omitting values
...
@@ -700,6 +749,7 @@ Omitting values
In many use-cases one doesn't care what the actual value of an enumeration
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:
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 instances of :class:`object` as the value
- use a descriptive string as the value
- use a descriptive string as the value
- use a tuple as the value and a custom :meth:`__new__` to replace the
- use a tuple as the value and a custom :meth:`__new__` to replace the
...
@@ -718,6 +768,20 @@ the (unimportant) value::
...
@@ -718,6 +768,20 @@ the (unimportant) value::
...
...
Using :class:`auto`
"""""""""""""""""""
Using :class:`object` would look like::
>>> class Color(NoValue):
... red = auto()
... blue = auto()
... green = auto()
...
>>> Color.green
<Color.green>
Using :class:`object`
Using :class:`object`
"""""""""""""""""""""
"""""""""""""""""""""
...
@@ -930,8 +994,11 @@ Supported ``_sunder_`` names
...
@@ -930,8 +994,11 @@ Supported ``_sunder_`` names
overridden
overridden
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent
(class attribute, removed during class creation)
(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
.. 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
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
be provided. It will be checked against the actual order of the enumeration
...
...
Lib/enum.py
View file @
48bc4418
...
@@ -10,7 +10,11 @@ except ImportError:
...
@@ -10,7 +10,11 @@ except ImportError:
from
collections
import
OrderedDict
from
collections
import
OrderedDict
__all__
=
[
'EnumMeta'
,
'Enum'
,
'IntEnum'
,
'Flag'
,
'IntFlag'
,
'unique'
]
__all__
=
[
'EnumMeta'
,
'Enum'
,
'IntEnum'
,
'Flag'
,
'IntFlag'
,
'auto'
,
'unique'
,
]
def
_is_descriptor
(
obj
):
def
_is_descriptor
(
obj
):
...
@@ -36,7 +40,6 @@ def _is_sunder(name):
...
@@ -36,7 +40,6 @@ def _is_sunder(name):
name
[
-
2
:
-
1
]
!=
'_'
and
name
[
-
2
:
-
1
]
!=
'_'
and
len
(
name
)
>
2
)
len
(
name
)
>
2
)
def
_make_class_unpicklable
(
cls
):
def
_make_class_unpicklable
(
cls
):
"""Make the given class un-picklable."""
"""Make the given class un-picklable."""
def
_break_on_call_reduce
(
self
,
proto
):
def
_break_on_call_reduce
(
self
,
proto
):
...
@@ -44,6 +47,12 @@ def _make_class_unpicklable(cls):
...
@@ -44,6 +47,12 @@ def _make_class_unpicklable(cls):
cls
.
__reduce_ex__
=
_break_on_call_reduce
cls
.
__reduce_ex__
=
_break_on_call_reduce
cls
.
__module__
=
'<unknown>'
cls
.
__module__
=
'<unknown>'
class
auto
:
"""
Instances are replaced with an appropriate value in Enum class suites.
"""
pass
class
_EnumDict
(
dict
):
class
_EnumDict
(
dict
):
"""Track enum member order and ensure member names are not reused.
"""Track enum member order and ensure member names are not reused.
...
@@ -55,6 +64,7 @@ class _EnumDict(dict):
...
@@ -55,6 +64,7 @@ class _EnumDict(dict):
def
__init__
(
self
):
def
__init__
(
self
):
super
().
__init__
()
super
().
__init__
()
self
.
_member_names
=
[]
self
.
_member_names
=
[]
self
.
_last_values
=
[]
def
__setitem__
(
self
,
key
,
value
):
def
__setitem__
(
self
,
key
,
value
):
"""Changes anything not dundered or not a descriptor.
"""Changes anything not dundered or not a descriptor.
...
@@ -71,6 +81,8 @@ class _EnumDict(dict):
...
@@ -71,6 +81,8 @@ class _EnumDict(dict):
'_generate_next_value_'
,
'_missing_'
,
'_generate_next_value_'
,
'_missing_'
,
):
):
raise
ValueError
(
'_names_ are reserved for future Enum use'
)
raise
ValueError
(
'_names_ are reserved for future Enum use'
)
if
key
==
'_generate_next_value_'
:
setattr
(
self
,
'_generate_next_value'
,
value
)
elif
_is_dunder
(
key
):
elif
_is_dunder
(
key
):
if
key
==
'__order__'
:
if
key
==
'__order__'
:
key
=
'_order_'
key
=
'_order_'
...
@@ -81,11 +93,13 @@ class _EnumDict(dict):
...
@@ -81,11 +93,13 @@ class _EnumDict(dict):
if
key
in
self
:
if
key
in
self
:
# enum overwriting a descriptor?
# enum overwriting a descriptor?
raise
TypeError
(
'%r already defined as: %r'
%
(
key
,
self
[
key
]))
raise
TypeError
(
'%r already defined as: %r'
%
(
key
,
self
[
key
]))
if
isinstance
(
value
,
auto
):
value
=
self
.
_generate_next_value
(
key
,
1
,
len
(
self
.
_member_names
),
self
.
_last_values
[:])
self
.
_member_names
.
append
(
key
)
self
.
_member_names
.
append
(
key
)
self
.
_last_values
.
append
(
value
)
super
().
__setitem__
(
key
,
value
)
super
().
__setitem__
(
key
,
value
)
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
# 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.
# 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`
# This is also why there are checks in EnumMeta like `if Enum is not None`
...
@@ -366,10 +380,11 @@ class EnumMeta(type):
...
@@ -366,10 +380,11 @@ class EnumMeta(type):
names
=
names
.
replace
(
','
,
' '
).
split
()
names
=
names
.
replace
(
','
,
' '
).
split
()
if
isinstance
(
names
,
(
tuple
,
list
))
and
isinstance
(
names
[
0
],
str
):
if
isinstance
(
names
,
(
tuple
,
list
))
and
isinstance
(
names
[
0
],
str
):
original_names
,
names
=
names
,
[]
original_names
,
names
=
names
,
[]
last_value
=
None
last_value
s
=
[]
for
count
,
name
in
enumerate
(
original_names
):
for
count
,
name
in
enumerate
(
original_names
):
last_value
=
first_enum
.
_generate_next_value_
(
name
,
start
,
count
,
last_value
)
value
=
first_enum
.
_generate_next_value_
(
name
,
start
,
count
,
last_values
[:])
names
.
append
((
name
,
last_value
))
last_values
.
append
(
value
)
names
.
append
((
name
,
value
))
# Here, names is either an iterable of (name, value) or a mapping.
# Here, names is either an iterable of (name, value) or a mapping.
for
item
in
names
:
for
item
in
names
:
...
@@ -514,11 +529,15 @@ class Enum(metaclass=EnumMeta):
...
@@ -514,11 +529,15 @@ class Enum(metaclass=EnumMeta):
# still not found -- try _missing_ hook
# still not found -- try _missing_ hook
return
cls
.
_missing_
(
value
)
return
cls
.
_missing_
(
value
)
@
staticmethod
def
_generate_next_value_
(
name
,
start
,
count
,
last_values
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
for
last_value
in
reversed
(
last_values
):
if
not
count
:
try
:
return
last_value
+
1
except
TypeError
:
pass
else
:
return
start
return
start
return
last_value
+
1
@
classmethod
@
classmethod
def
_missing_
(
cls
,
value
):
def
_missing_
(
cls
,
value
):
raise
ValueError
(
"%r is not a valid %s"
%
(
value
,
cls
.
__name__
))
raise
ValueError
(
"%r is not a valid %s"
%
(
value
,
cls
.
__name__
))
...
@@ -616,8 +635,8 @@ def _reduce_ex_by_name(self, proto):
...
@@ -616,8 +635,8 @@ def _reduce_ex_by_name(self, proto):
class
Flag
(
Enum
):
class
Flag
(
Enum
):
"""Support for flags"""
"""Support for flags"""
@
staticmethod
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
s
):
"""
"""
Generate the next value when not given.
Generate the next value when not given.
...
@@ -628,7 +647,12 @@ class Flag(Enum):
...
@@ -628,7 +647,12 @@ class Flag(Enum):
"""
"""
if
not
count
:
if
not
count
:
return
start
if
start
is
not
None
else
1
return
start
if
start
is
not
None
else
1
high_bit
=
_high_bit
(
last_value
)
for
last_value
in
reversed
(
last_values
):
try
:
high_bit
=
_high_bit
(
last_value
)
break
except
TypeError
:
raise
TypeError
(
'Invalid Flag value: %r'
%
last_value
)
from
None
return
2
**
(
high_bit
+
1
)
return
2
**
(
high_bit
+
1
)
@
classmethod
@
classmethod
...
...
Lib/test/test_enum.py
View file @
48bc4418
...
@@ -3,7 +3,7 @@ import inspect
...
@@ -3,7 +3,7 @@ import inspect
import
pydoc
import
pydoc
import
unittest
import
unittest
from
collections
import
OrderedDict
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
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
...
@@ -113,6 +113,7 @@ class TestHelpers(unittest.TestCase):
...
@@ -113,6 +113,7 @@ class TestHelpers(unittest.TestCase):
'__'
,
'___'
,
'____'
,
'_____'
,):
'__'
,
'___'
,
'____'
,
'_____'
,):
self
.
assertFalse
(
enum
.
_is_dunder
(
s
))
self
.
assertFalse
(
enum
.
_is_dunder
(
s
))
# tests
class
TestEnum
(
unittest
.
TestCase
):
class
TestEnum
(
unittest
.
TestCase
):
...
@@ -1578,6 +1579,61 @@ class TestEnum(unittest.TestCase):
...
@@ -1578,6 +1579,61 @@ class TestEnum(unittest.TestCase):
self
.
assertEqual
(
LabelledList
.
unprocessed
,
1
)
self
.
assertEqual
(
LabelledList
.
unprocessed
,
1
)
self
.
assertEqual
(
LabelledList
(
1
),
LabelledList
.
unprocessed
)
self
.
assertEqual
(
LabelledList
(
1
),
LabelledList
.
unprocessed
)
def
test_auto_number
(
self
):
class
Color
(
Enum
):
red
=
auto
()
blue
=
auto
()
green
=
auto
()
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
blue
,
Color
.
green
])
self
.
assertEqual
(
Color
.
red
.
value
,
1
)
self
.
assertEqual
(
Color
.
blue
.
value
,
2
)
self
.
assertEqual
(
Color
.
green
.
value
,
3
)
def
test_auto_name
(
self
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last
):
return
name
red
=
auto
()
blue
=
auto
()
green
=
auto
()
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
blue
,
Color
.
green
])
self
.
assertEqual
(
Color
.
red
.
value
,
'red'
)
self
.
assertEqual
(
Color
.
blue
.
value
,
'blue'
)
self
.
assertEqual
(
Color
.
green
.
value
,
'green'
)
def
test_auto_name_inherit
(
self
):
class
AutoNameEnum
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last
):
return
name
class
Color
(
AutoNameEnum
):
red
=
auto
()
blue
=
auto
()
green
=
auto
()
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
blue
,
Color
.
green
])
self
.
assertEqual
(
Color
.
red
.
value
,
'red'
)
self
.
assertEqual
(
Color
.
blue
.
value
,
'blue'
)
self
.
assertEqual
(
Color
.
green
.
value
,
'green'
)
def
test_auto_garbage
(
self
):
class
Color
(
Enum
):
red
=
'red'
blue
=
auto
()
self
.
assertEqual
(
Color
.
blue
.
value
,
1
)
def
test_auto_garbage_corrected
(
self
):
class
Color
(
Enum
):
red
=
'red'
blue
=
2
green
=
auto
()
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
blue
,
Color
.
green
])
self
.
assertEqual
(
Color
.
red
.
value
,
'red'
)
self
.
assertEqual
(
Color
.
blue
.
value
,
2
)
self
.
assertEqual
(
Color
.
green
.
value
,
3
)
class
TestOrder
(
unittest
.
TestCase
):
class
TestOrder
(
unittest
.
TestCase
):
...
@@ -1856,7 +1912,6 @@ class TestFlag(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
.
CURLY
|
FlagStooges
.
MOE
)
test_pickle_dump_load
(
self
.
assertIs
,
FlagStooges
)
test_pickle_dump_load
(
self
.
assertIs
,
FlagStooges
)
def
test_containment
(
self
):
def
test_containment
(
self
):
Perm
=
self
.
Perm
Perm
=
self
.
Perm
R
,
W
,
X
=
Perm
R
,
W
,
X
=
Perm
...
@@ -1877,6 +1932,24 @@ class TestFlag(unittest.TestCase):
...
@@ -1877,6 +1932,24 @@ class TestFlag(unittest.TestCase):
self
.
assertFalse
(
W
in
RX
)
self
.
assertFalse
(
W
in
RX
)
self
.
assertFalse
(
X
in
RW
)
self
.
assertFalse
(
X
in
RW
)
def
test_auto_number
(
self
):
class
Color
(
Flag
):
red
=
auto
()
blue
=
auto
()
green
=
auto
()
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
blue
,
Color
.
green
])
self
.
assertEqual
(
Color
.
red
.
value
,
1
)
self
.
assertEqual
(
Color
.
blue
.
value
,
2
)
self
.
assertEqual
(
Color
.
green
.
value
,
4
)
def
test_auto_number_garbage
(
self
):
with
self
.
assertRaisesRegex
(
TypeError
,
'Invalid Flag value: .not an int.'
):
class
Color
(
Flag
):
red
=
'not an int'
blue
=
auto
()
class
TestIntFlag
(
unittest
.
TestCase
):
class
TestIntFlag
(
unittest
.
TestCase
):
"""Tests of the IntFlags."""
"""Tests of the IntFlags."""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment