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
73fc586d
Commit
73fc586d
authored
Aug 05, 2016
by
Ethan Furman
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add AutoEnum: automatically provides next value if missing. Issue 26988.
parent
20bd9f03
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
663 additions
and
79 deletions
+663
-79
Doc/library/enum.rst
Doc/library/enum.rst
+216
-65
Lib/enum.py
Lib/enum.py
+122
-13
Lib/test/test_enum.py
Lib/test/test_enum.py
+323
-1
Misc/NEWS
Misc/NEWS
+2
-0
No files found.
Doc/library/enum.rst
View file @
73fc586d
...
...
@@ -37,6 +37,13 @@ one decorator, :func:`unique`.
Base class for creating enumerated constants that are also
subclasses of :class:`int`.
.. class:: AutoEnum
Base class for creating automatically numbered members (may
be combined with IntEnum if desired).
.. versionadded:: 3.6
.. function:: unique
Enum class decorator that ensures only one name is bound to any one value.
...
...
@@ -47,14 +54,14 @@ Creating an Enum
Enumerations are created using the :keyword:`class` syntax, which makes them
easy to read and write. An alternative creation method is described in
`Functional API`_. To define a
n enumeration, subclass :class:`Enum` as
follows::
>>> from enum import Enum
>>> class Color(Enum):
... red
= 1
... green
= 2
... blue
= 3
`Functional API`_. To define a
simple enumeration, subclass :class:`AutoEnum`
as
follows::
>>> from enum import
Auto
Enum
>>> class Color(
Auto
Enum):
... red
... green
... blue
...
.. note:: Nomenclature
...
...
@@ -72,6 +79,33 @@ follows::
are not normal Python classes. See `How are Enums different?`_ for
more details.
To create your own automatic :class:`Enum` classes, you need to add a
:meth:`_generate_next_value_` method; it will be used to create missing values
for any members after its definition.
.. versionadded:: 3.6
If you need full control of the member values, use :class:`Enum` as the base
class and specify the values manually::
>>> from enum import Enum
>>> class Color(Enum):
... red = 19
... green = 7.9182
... blue = 'periwinkle'
...
We'll use the following Enum for the examples below::
>>> class Color(Enum):
... red = 1
... green = 2
... blue = 3
...
Enum Details
------------
Enumeration members have human readable string representations::
>>> print(Color.red)
...
...
@@ -235,7 +269,11 @@ aliases::
The ``__members__`` attribute can be used for detailed programmatic access to
the enumeration members. For example, finding all the aliases::
>>> [name for name, member in Shape.__members__.items() if member.name != name]
>>> [
... name
... for name, member in Shape.__members__.items()
... if member.name != name
... ]
['alias_for_square']
...
...
@@ -257,7 +295,7 @@ members are not integers (but see `IntEnum`_ below)::
>>> Color.red < Color.blue
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError:
unorderable types: Color() < Color()
TypeError:
'<' not supported between instances of 'Color' and 'Color'
Equality comparisons are defined though::
...
...
@@ -280,10 +318,10 @@ Allowed members and attributes of enumerations
----------------------------------------------
The examples above use integers for enumeration values. Using integers is
short and handy (and provided by default by
the `Functional API`_), but not
strictly enforced. In the vast majority of use-cases, one doesn't care what
the actual value of an enumeration is. But if the value *is* important,
enumerations can have arbitrary values.
short and handy (and provided by default by
:class:`AutoEnum` and the
`Functional API`_), but not strictly enforced. In the vast majority of
use-cases, one doesn't care what the actual value of an enumeration is.
But if the value *is* important,
enumerations can have arbitrary values.
Enumerations are Python classes, and can have methods and special methods as
usual. If we have this enumeration::
...
...
@@ -393,17 +431,21 @@ The :class:`Enum` class is callable, providing the following functional API::
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]
The semantics of this API resemble :class:`~collections.namedtuple`. The first
argument of the call to :class:`Enum` is the name of the enumeration.
The semantics of this API resemble :class:`~collections.namedtuple`.
- the first argument of the call to :class:`Enum` is the name of the
enumeration;
- the second argument is the *source* of enumeration member names. It can be a
whitespace-separated string of names, a sequence of names, a sequence of
2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to
values;
The second argument is the *source* of enumeration member names. It can be a
whitespace-separated string of names, a sequence of names, a sequence of
2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to
values. The last two options enable assigning arbitrary values to
enumerations; the others auto-assign increasing integers starting with 1 (use
the ``start`` parameter to specify a different starting value). A
new class derived from :class:`Enum` is returned. In other words, the above
assignment to :class:`Animal` is equivalent to::
- the last two options enable assigning arbitrary values to enumerations; the
others auto-assign increasing integers starting with 1 (use the ``start``
parameter to specify a different starting value). A new class derived from
:class:`Enum` is returned. In other words, the above assignment to
:class:`Animal` is equivalent to::
>>> class Animal(Enum):
... ant = 1
...
...
@@ -419,7 +461,7 @@ to ``True``.
Pickling enums created with the functional API can be tricky as frame stack
implementation details are used to try and figure out which module the
enumeration is being created in (e.g. it will fail if you use a utility
function in separate module, and also may not work on IronPython or Jython).
function in
a
separate module, and also may not work on IronPython or Jython).
The solution is to specify the module name explicitly as follows::
>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)
...
...
@@ -439,7 +481,15 @@ SomeData in the global scope::
The complete signature is::
Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
Enum(
value='NewEnumName',
names=<...>,
*,
module='...',
qualname='...',
type=<mixed-in class>,
start=1,
)
:value: What the new Enum class will record as its name.
...
...
@@ -475,10 +525,41 @@ The complete signature is::
Derived Enumerations
--------------------
AutoEnum
^^^^^^^^
This version of :class:`Enum` automatically assigns numbers as the values
for the enumeration members, while still allowing values to be specified
when needed::
>>> from enum import AutoEnum
>>> class Color(AutoEnum):
... red
... green = 5
... blue
...
>>> list(Color)
[<Color.red: 1>, <Color.green: 5>, <Color.blue: 6>]
.. note:: Name Lookup
By default the names :func:`property`, :func:`classmethod`, and
:func:`staticmethod` are shielded from becoming members. To enable
them, or to specify a different set of shielded names, specify the
ignore parameter::
>>> class AddressType(AutoEnum, ignore='classmethod staticmethod'):
... pobox
... mailbox
... property
...
.. versionadded:: 3.6
IntEnum
^^^^^^^
A
variation of :class:`Enum` is provided
which is also a subclass of
A
nother variation of :class:`Enum`
which is also a subclass of
:class:`int`. Members of an :class:`IntEnum` can be compared to integers;
by extension, integer enumerations of different types can also be compared
to each other::
...
...
@@ -521,14 +602,13 @@ However, they still can't be compared to standard :class:`Enum` enumerations::
>>> [i for i in range(Shape.square)]
[0, 1]
For the vast majority of code, :class:`Enum` is strongly recommended,
since :class:`IntEnum` breaks some semantic promises of an enumeration (by
being comparable to integers, and thus by transitivity to other
unrelated enumerations). It should be used only in special cases where
there's no other choice; for example, when integer constants are
replaced with enumerations and backwards compatibility is required with code
that still expects integers.
For the vast majority of code, :class:`Enum` and :class:`AutoEnum` are strongly
recommended, since :class:`IntEnum` breaks some semantic promises of an
enumeration (by being comparable to integers, and thus by transitivity to other
unrelated ``IntEnum`` enumerations). It should be used only in special cases
where there's no other choice; for example, when integer constants are replaced
with enumerations and backwards compatibility is required with code that still
expects integers.
Others
^^^^^^
...
...
@@ -540,7 +620,9 @@ simple to implement independently::
pass
This demonstrates how similar derived enumerations can be defined; for example
a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`.
an :class:`AutoIntEnum` that mixes in :class:`int` with :class:`AutoEnum`
to get members that are :class:`int` (but keep in mind the warnings for
:class:`IntEnum`).
Some rules:
...
...
@@ -567,31 +649,35 @@ Some rules:
Interesting examples
--------------------
While :class:`Enum`
and :class:`IntEnum` are expected to cover the majority of
use-cases, they cannot cover them all. Here are recipes for some different
types of enumerations that can be used directly, or as examples for creating
one's own.
While :class:`Enum`
, :class:`AutoEnum`, and :class:`IntEnum` are expected
to cover the majority of use-cases, they cannot cover them all. Here are
recipes for some different types of enumerations that can be used directly,
o
r as examples for creating o
ne's own.
Auto
Number
^^^^^^^^^^
Auto
DocEnum
^^^^^^^^^^
^
Avoids having to specify the value for each enumeration member::
Automatically numbers the members, and uses the given value as the
:attr:`__doc__` string::
>>> class Auto
Number
(Enum):
... def __new__(cls):
>>> class Auto
DocEnum
(Enum):
... def __new__(cls
, doc
):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... obj.__doc__ = doc
... return obj
...
>>> class Color(Auto
Number
):
... red =
()
... green =
()
... blue =
()
>>> class Color(Auto
DocEnum
):
... red =
'stop'
... green =
'go'
... blue =
'what?'
...
>>> Color.green.value == 2
True
>>> Color.green.__doc__
'go'
.. note::
...
...
@@ -599,6 +685,23 @@ Avoids having to specify the value for each enumeration member::
members; it is then replaced by Enum's :meth:`__new__` which is used after
class creation for lookup of existing members.
AutoNameEnum
^^^^^^^^^^^^
Automatically sets the member's value to its name::
>>> class AutoNameEnum(Enum):
... def _generate_next_value_(name, start, count, last_value):
... return name
...
>>> class Color(AutoNameEnum):
... red
... green
... blue
...
>>> Color.green.value == 'green'
True
OrderedEnum
^^^^^^^^^^^
...
...
@@ -731,10 +834,61 @@ member instances.
Finer Points
^^^^^^^^^^^^
:class:`Enum` members are instances of an :class:`Enum` class, and even
though they are accessible as `EnumClass.member`, they should not be accessed
Enum class signature
~~~~~~~~~~~~~~~~~~~~
``class SomeName(
AnEnum,
start=None,
ignore='staticmethod classmethod property',
):``
``start`` can be used by a :meth:`_generate_next_value_` method to specify a
starting value.
``ignore`` specifies which names, if any, will not attempt to auto-generate
a new value (they will also be removed from the class body).
Supported ``__dunder__`` names
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :attr:`__members__` attribute is only available on the class.
:meth:`__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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ``_order_`` -- used in Python 2/3 code to ensure member order is consistent [class attribute]
- ``_name_`` -- name of the member (but use ``name`` for normal access)
- ``_value_`` -- value of the member; can be set / modified in ``__new__`` (see ``_name_``)
- ``_missing_`` -- a lookup function used when a value is not found (only after class creation)
- ``_generate_next_value_`` -- a function to generate missing values (only during class creation)
:meth:`_generate_next_value_` signature
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``def _generate_next_value_(name, start, count, last_value):``
- ``name`` is the name of the member
- ``start`` is the initital start value (if any) or None
- ``count`` is the number of existing members in the enumeration
- ``last_value`` is the value of the last enum member (if any) or None
Enum member type
~~~~~~~~~~~~~~~~
``Enum`` members are instances of an ``Enum`` class, and even
though they are accessible as ``EnumClass.member``, they should not be accessed
directly from the member as that lookup may fail or, worse, return something
besides the
:class:`Enum` member you
looking for::
besides the
``Enum`` member you are
looking for::
>>> class FieldTypes(Enum):
... name = 0
...
...
@@ -748,18 +902,24 @@ besides the :class:`Enum` member you looking for::
.. versionchanged:: 3.5
Boolean evaluation: Enum classes that are mixed with non-Enum types (such as
Boolean value of ``Enum`` classes and members
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Enum classes that are mixed with non-Enum types (such as
:class:`int`, :class:`str`, etc.) are evaluated according to the mixed-in
type's rules; otherwise, all members evaluate as
``True`
`. To make your own
type's rules; otherwise, all members evaluate as
:data:`True
`. To make your own
Enum's boolean evaluation depend on the member's value add the following to
your class::
def __bool__(self):
return bool(self.value)
The :attr:`__members__` attribute is only available on the class.
If you give your :class:`Enum` subclass extra methods, like the `Planet`_
Enum classes with methods
~~~~~~~~~~~~~~~~~~~~~~~~~
If you give your ``Enum`` subclass extra methods, like the `Planet`_
class above, those methods will show up in a :func:`dir` of the member,
but not of the class::
...
...
@@ -767,12 +927,3 @@ but not of the class::
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']
The :meth:`__new__` method will only be used for the creation of the
:class:`Enum` members -- after that it is replaced. Any custom :meth:`__new__`
method must create the object and set the :attr:`_value_` attribute
appropriately.
If you wish to change how :class:`Enum` members are looked up you should either
write a helper function or a :func:`classmethod` for the :class:`Enum`
subclass.
Lib/enum.py
View file @
73fc586d
...
...
@@ -8,7 +8,9 @@ except ImportError:
from
collections
import
OrderedDict
__all__
=
[
'EnumMeta'
,
'Enum'
,
'IntEnum'
,
'unique'
]
__all__
=
[
'EnumMeta'
,
'Enum'
,
'IntEnum'
,
'AutoEnum'
,
'unique'
,
]
def
_is_descriptor
(
obj
):
...
...
@@ -52,7 +54,30 @@ class _EnumDict(dict):
"""
def
__init__
(
self
):
super
().
__init__
()
# list of enum members
self
.
_member_names
=
[]
# starting value
self
.
_start
=
None
# last assigned value
self
.
_last_value
=
None
# when the magic turns off
self
.
_locked
=
True
# list of temporary names
self
.
_ignore
=
[]
def
__getitem__
(
self
,
key
):
if
(
self
.
_generate_next_value_
is
None
or
self
.
_locked
or
key
in
self
or
key
in
self
.
_ignore
or
_is_sunder
(
key
)
or
_is_dunder
(
key
)
):
return
super
(
_EnumDict
,
self
).
__getitem__
(
key
)
next_value
=
self
.
_generate_next_value_
(
key
,
self
.
_start
,
len
(
self
.
_member_names
),
self
.
_last_value
)
self
[
key
]
=
next_value
return
next_value
def
__setitem__
(
self
,
key
,
value
):
"""Changes anything not dundered or not a descriptor.
...
...
@@ -64,19 +89,55 @@ class _EnumDict(dict):
"""
if
_is_sunder
(
key
):
raise
ValueError
(
'_names_ are reserved for future Enum use'
)
if
key
not
in
(
'_settings_'
,
'_order_'
,
'_ignore_'
,
'_start_'
,
'_generate_next_value_'
):
raise
ValueError
(
'_names_ are reserved for future Enum use'
)
elif
key
==
'_generate_next_value_'
:
if
isinstance
(
value
,
staticmethod
):
value
=
value
.
__get__
(
None
,
self
)
self
.
_generate_next_value_
=
value
self
.
_locked
=
False
elif
key
==
'_ignore_'
:
if
isinstance
(
value
,
str
):
value
=
value
.
split
()
else
:
value
=
list
(
value
)
self
.
_ignore
=
value
already
=
set
(
value
)
&
set
(
self
.
_member_names
)
if
already
:
raise
ValueError
(
'_ignore_ cannot specify already set names: %r'
%
(
already
,
))
elif
key
==
'_start_'
:
self
.
_start
=
value
self
.
_locked
=
False
elif
_is_dunder
(
key
):
pass
if
key
==
'__order__'
:
key
=
'_order_'
if
_is_descriptor
(
value
):
self
.
_locked
=
True
elif
key
in
self
.
_member_names
:
# descriptor overwriting an enum?
raise
TypeError
(
'Attempted to reuse key: %r'
%
key
)
elif
key
in
self
.
_ignore
:
pass
elif
not
_is_descriptor
(
value
):
if
key
in
self
:
# enum overwriting a descriptor?
raise
TypeError
(
'
Key already defined as: %r'
%
self
[
key
]
)
raise
TypeError
(
'
%r already defined as: %r'
%
(
key
,
self
[
key
])
)
self
.
_member_names
.
append
(
key
)
if
self
.
_generate_next_value_
is
not
None
:
self
.
_last_value
=
value
else
:
# not a new member, turn off the autoassign magic
self
.
_locked
=
True
super
().
__setitem__
(
key
,
value
)
# for magic "auto values" an Enum class should specify a `_generate_next_value_`
# method; that method will be used to generate missing values, and is
# implicitly a staticmethod;
# the signature should be `def _generate_next_value_(name, last_value)`
# last_value will be the last value created and/or assigned, or None
_generate_next_value_
=
None
# Dummy value for Enum as EnumMeta explicitly checks for it, but of course
...
...
@@ -84,14 +145,31 @@ class _EnumDict(dict):
# This is also why there are checks in EnumMeta like `if Enum is not None`
Enum
=
None
_ignore_sentinel
=
object
()
class
EnumMeta
(
type
):
"""Metaclass for Enum"""
@
classmethod
def
__prepare__
(
metacls
,
cls
,
bases
):
return
_EnumDict
()
def
__new__
(
metacls
,
cls
,
bases
,
classdict
):
def
__prepare__
(
metacls
,
cls
,
bases
,
start
=
None
,
ignore
=
_ignore_sentinel
):
# create the namespace dict
enum_dict
=
_EnumDict
()
# inherit previous flags and _generate_next_value_ function
member_type
,
first_enum
=
metacls
.
_get_mixins_
(
bases
)
if
first_enum
is
not
None
:
enum_dict
[
'_generate_next_value_'
]
=
getattr
(
first_enum
,
'_generate_next_value_'
,
None
)
if
start
is
None
:
start
=
getattr
(
first_enum
,
'_start_'
,
None
)
if
ignore
is
_ignore_sentinel
:
enum_dict
[
'_ignore_'
]
=
'property classmethod staticmethod'
.
split
()
elif
ignore
:
enum_dict
[
'_ignore_'
]
=
ignore
if
start
is
not
None
:
enum_dict
[
'_start_'
]
=
start
return
enum_dict
def
__init__
(
cls
,
*
args
,
**
kwds
):
super
(
EnumMeta
,
cls
).
__init__
(
*
args
)
def
__new__
(
metacls
,
cls
,
bases
,
classdict
,
**
kwds
):
# an Enum class is final once enumeration items have been defined; it
# cannot be mixed with other types (int, float, etc.) if it has an
# inherited __new__ unless a new __new__ is defined (or the resulting
...
...
@@ -102,12 +180,24 @@ class EnumMeta(type):
# save enum items into separate mapping so they don't get baked into
# the new class
members
=
{
k
:
classdict
[
k
]
for
k
in
classdict
.
_member_names
}
enum_
members
=
{
k
:
classdict
[
k
]
for
k
in
classdict
.
_member_names
}
for
name
in
classdict
.
_member_names
:
del
classdict
[
name
]
# adjust the sunders
_order_
=
classdict
.
pop
(
'_order_'
,
None
)
classdict
.
pop
(
'_ignore_'
,
None
)
# py3 support for definition order (helps keep py2/py3 code in sync)
if
_order_
is
not
None
:
if
isinstance
(
_order_
,
str
):
_order_
=
_order_
.
replace
(
','
,
' '
).
split
()
unique_members
=
[
n
for
n
in
clsdict
.
_member_names
if
n
in
_order_
]
if
_order_
!=
unique_members
:
raise
TypeError
(
'member order does not match _order_'
)
# check for illegal enum names (any others?)
invalid_names
=
set
(
members
)
&
{
'mro'
,
}
invalid_names
=
set
(
enum_
members
)
&
{
'mro'
,
}
if
invalid_names
:
raise
ValueError
(
'Invalid enum member name: {0}'
.
format
(
','
.
join
(
invalid_names
)))
...
...
@@ -151,7 +241,7 @@ class EnumMeta(type):
# a custom __new__ is doing something funky with the values -- such as
# auto-numbering ;)
for
member_name
in
classdict
.
_member_names
:
value
=
members
[
member_name
]
value
=
enum_
members
[
member_name
]
if
not
isinstance
(
value
,
tuple
):
args
=
(
value
,
)
else
:
...
...
@@ -165,7 +255,10 @@ class EnumMeta(type):
else
:
enum_member
=
__new__
(
enum_class
,
*
args
)
if
not
hasattr
(
enum_member
,
'_value_'
):
enum_member
.
_value_
=
member_type
(
*
args
)
if
member_type
is
object
:
enum_member
.
_value_
=
value
else
:
enum_member
.
_value_
=
member_type
(
*
args
)
value
=
enum_member
.
_value_
enum_member
.
_name_
=
member_name
enum_member
.
__objclass__
=
enum_class
...
...
@@ -572,6 +665,22 @@ class IntEnum(int, Enum):
def
_reduce_ex_by_name
(
self
,
proto
):
return
self
.
name
class
AutoEnum
(
Enum
):
"""Enum where values are automatically assigned."""
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
"""
Generate the next value when not given.
name: the name of the member
start: the initital start value or None
count: the number of existing members
last_value: the last value assigned or None
"""
# add one to the last assigned value
if
not
count
:
return
start
if
start
is
not
None
else
1
return
last_value
+
1
def
unique
(
enumeration
):
"""Class decorator for enumerations ensuring unique member values."""
duplicates
=
[]
...
...
Lib/test/test_enum.py
View file @
73fc586d
...
...
@@ -3,7 +3,7 @@ import inspect
import
pydoc
import
unittest
from
collections
import
OrderedDict
from
enum
import
Enum
,
IntEnum
,
EnumMeta
,
unique
from
enum
import
Enum
Meta
,
Enum
,
IntEnum
,
AutoEnum
,
unique
from
io
import
StringIO
from
pickle
import
dumps
,
loads
,
PicklingError
,
HIGHEST_PROTOCOL
from
test
import
support
...
...
@@ -1570,6 +1570,328 @@ class TestEnum(unittest.TestCase):
self
.
assertEqual
(
LabelledList
.
unprocessed
,
1
)
self
.
assertEqual
(
LabelledList
(
1
),
LabelledList
.
unprocessed
)
def
test_ignore_as_str
(
self
):
from
datetime
import
timedelta
class
Period
(
Enum
,
ignore
=
'Period i'
):
"""
different lengths of time
"""
def
__new__
(
cls
,
value
,
period
):
obj
=
object
.
__new__
(
cls
)
obj
.
_value_
=
value
obj
.
period
=
period
return
obj
Period
=
vars
()
for
i
in
range
(
367
):
Period
[
'Day%d'
%
i
]
=
timedelta
(
days
=
i
),
'day'
for
i
in
range
(
53
):
Period
[
'Week%d'
%
i
]
=
timedelta
(
days
=
i
*
7
),
'week'
for
i
in
range
(
13
):
Period
[
'Month%d'
%
i
]
=
i
,
'month'
OneDay
=
Day1
OneWeek
=
Week1
self
.
assertEqual
(
Period
.
Day7
.
value
,
timedelta
(
days
=
7
))
self
.
assertEqual
(
Period
.
Day7
.
period
,
'day'
)
def
test_ignore_as_list
(
self
):
from
datetime
import
timedelta
class
Period
(
Enum
,
ignore
=
[
'Period'
,
'i'
]):
"""
different lengths of time
"""
def
__new__
(
cls
,
value
,
period
):
obj
=
object
.
__new__
(
cls
)
obj
.
_value_
=
value
obj
.
period
=
period
return
obj
Period
=
vars
()
for
i
in
range
(
367
):
Period
[
'Day%d'
%
i
]
=
timedelta
(
days
=
i
),
'day'
for
i
in
range
(
53
):
Period
[
'Week%d'
%
i
]
=
timedelta
(
days
=
i
*
7
),
'week'
for
i
in
range
(
13
):
Period
[
'Month%d'
%
i
]
=
i
,
'month'
OneDay
=
Day1
OneWeek
=
Week1
self
.
assertEqual
(
Period
.
Day7
.
value
,
timedelta
(
days
=
7
))
self
.
assertEqual
(
Period
.
Day7
.
period
,
'day'
)
def
test_new_with_no_value_and_int_base_class
(
self
):
class
NoValue
(
int
,
Enum
):
def
__new__
(
cls
,
value
):
obj
=
int
.
__new__
(
cls
,
value
)
obj
.
index
=
len
(
cls
.
__members__
)
return
obj
this
=
1
that
=
2
self
.
assertEqual
(
list
(
NoValue
),
[
NoValue
.
this
,
NoValue
.
that
])
self
.
assertEqual
(
NoValue
.
this
,
1
)
self
.
assertEqual
(
NoValue
.
this
.
value
,
1
)
self
.
assertEqual
(
NoValue
.
this
.
index
,
0
)
self
.
assertEqual
(
NoValue
.
that
,
2
)
self
.
assertEqual
(
NoValue
.
that
.
value
,
2
)
self
.
assertEqual
(
NoValue
.
that
.
index
,
1
)
def
test_new_with_no_value
(
self
):
class
NoValue
(
Enum
):
def
__new__
(
cls
,
value
):
obj
=
object
.
__new__
(
cls
)
obj
.
index
=
len
(
cls
.
__members__
)
return
obj
this
=
1
that
=
2
self
.
assertEqual
(
list
(
NoValue
),
[
NoValue
.
this
,
NoValue
.
that
])
self
.
assertEqual
(
NoValue
.
this
.
value
,
1
)
self
.
assertEqual
(
NoValue
.
this
.
index
,
0
)
self
.
assertEqual
(
NoValue
.
that
.
value
,
2
)
self
.
assertEqual
(
NoValue
.
that
.
index
,
1
)
class
TestAutoNumber
(
unittest
.
TestCase
):
def
test_autonumbering
(
self
):
class
Color
(
AutoEnum
):
red
green
blue
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
green
,
Color
.
blue
])
self
.
assertEqual
(
Color
.
red
.
value
,
1
)
self
.
assertEqual
(
Color
.
green
.
value
,
2
)
self
.
assertEqual
(
Color
.
blue
.
value
,
3
)
def
test_autointnumbering
(
self
):
class
Color
(
int
,
AutoEnum
):
red
green
blue
self
.
assertTrue
(
isinstance
(
Color
.
red
,
int
))
self
.
assertEqual
(
Color
.
green
,
2
)
self
.
assertTrue
(
Color
.
blue
>
Color
.
red
)
def
test_autonumbering_with_start
(
self
):
class
Color
(
AutoEnum
,
start
=
7
):
red
green
blue
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
green
,
Color
.
blue
])
self
.
assertEqual
(
Color
.
red
.
value
,
7
)
self
.
assertEqual
(
Color
.
green
.
value
,
8
)
self
.
assertEqual
(
Color
.
blue
.
value
,
9
)
def
test_autonumbering_with_start_and_skip
(
self
):
class
Color
(
AutoEnum
,
start
=
7
):
red
green
blue
=
11
brown
self
.
assertEqual
(
list
(
Color
),
[
Color
.
red
,
Color
.
green
,
Color
.
blue
,
Color
.
brown
])
self
.
assertEqual
(
Color
.
red
.
value
,
7
)
self
.
assertEqual
(
Color
.
green
.
value
,
8
)
self
.
assertEqual
(
Color
.
blue
.
value
,
11
)
self
.
assertEqual
(
Color
.
brown
.
value
,
12
)
def
test_badly_overridden_ignore
(
self
):
with
self
.
assertRaisesRegex
(
TypeError
,
"'int' object is not callable"
):
class
Color
(
AutoEnum
):
_ignore_
=
()
red
green
blue
@
property
def
whatever
(
self
):
pass
with
self
.
assertRaisesRegex
(
TypeError
,
"'int' object is not callable"
):
class
Color
(
AutoEnum
,
ignore
=
None
):
red
green
blue
@
property
def
whatever
(
self
):
pass
with
self
.
assertRaisesRegex
(
TypeError
,
"'int' object is not callable"
):
class
Color
(
AutoEnum
,
ignore
=
'classmethod staticmethod'
):
red
green
blue
@
property
def
whatever
(
self
):
pass
def
test_property
(
self
):
class
Color
(
AutoEnum
):
red
green
blue
@
property
def
cap_name
(
self
):
return
self
.
name
.
title
()
self
.
assertEqual
(
Color
.
blue
.
cap_name
,
'Blue'
)
def
test_magic_turns_off
(
self
):
with
self
.
assertRaisesRegex
(
NameError
,
"brown"
):
class
Color
(
AutoEnum
):
red
green
blue
@
property
def
cap_name
(
self
):
return
self
.
name
.
title
()
brown
with
self
.
assertRaisesRegex
(
NameError
,
"rose"
):
class
Color
(
AutoEnum
):
red
green
blue
def
hello
(
self
):
print
(
'Hello! My serial is %s.'
%
self
.
value
)
rose
with
self
.
assertRaisesRegex
(
NameError
,
"cyan"
):
class
Color
(
AutoEnum
):
red
green
blue
def
__init__
(
self
,
*
args
):
pass
cyan
class
TestGenerateMethod
(
unittest
.
TestCase
):
def
test_autonaming
(
self
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
Red
Green
Blue
self
.
assertEqual
(
list
(
Color
),
[
Color
.
Red
,
Color
.
Green
,
Color
.
Blue
])
self
.
assertEqual
(
Color
.
Red
.
value
,
'Red'
)
self
.
assertEqual
(
Color
.
Green
.
value
,
'Green'
)
self
.
assertEqual
(
Color
.
Blue
.
value
,
'Blue'
)
def
test_autonamestr
(
self
):
class
Color
(
str
,
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
Red
Green
Blue
self
.
assertTrue
(
isinstance
(
Color
.
Red
,
str
))
self
.
assertEqual
(
Color
.
Green
,
'Green'
)
self
.
assertTrue
(
Color
.
Blue
<
Color
.
Red
)
def
test_generate_as_staticmethod
(
self
):
class
Color
(
str
,
Enum
):
@
staticmethod
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
.
lower
()
Red
Green
Blue
self
.
assertTrue
(
isinstance
(
Color
.
Red
,
str
))
self
.
assertEqual
(
Color
.
Green
,
'green'
)
self
.
assertTrue
(
Color
.
Blue
<
Color
.
Red
)
def
test_overridden_ignore
(
self
):
with
self
.
assertRaisesRegex
(
TypeError
,
"'str' object is not callable"
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
_ignore_
=
()
red
green
blue
@
property
def
whatever
(
self
):
pass
with
self
.
assertRaisesRegex
(
TypeError
,
"'str' object is not callable"
):
class
Color
(
Enum
,
ignore
=
None
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
red
green
blue
@
property
def
whatever
(
self
):
pass
def
test_property
(
self
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
red
green
blue
@
property
def
upper_name
(
self
):
return
self
.
name
.
upper
()
self
.
assertEqual
(
Color
.
blue
.
upper_name
,
'BLUE'
)
def
test_magic_turns_off
(
self
):
with
self
.
assertRaisesRegex
(
NameError
,
"brown"
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
red
green
blue
@
property
def
cap_name
(
self
):
return
self
.
name
.
title
()
brown
with
self
.
assertRaisesRegex
(
NameError
,
"rose"
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
red
green
blue
def
hello
(
self
):
print
(
'Hello! My value %s.'
%
self
.
value
)
rose
with
self
.
assertRaisesRegex
(
NameError
,
"cyan"
):
class
Color
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
name
red
green
blue
def
__init__
(
self
,
*
args
):
pass
cyan
def
test_powers_of_two
(
self
):
class
Bits
(
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
2
**
count
one
two
four
eight
self
.
assertEqual
(
Bits
.
one
.
value
,
1
)
self
.
assertEqual
(
Bits
.
two
.
value
,
2
)
self
.
assertEqual
(
Bits
.
four
.
value
,
4
)
self
.
assertEqual
(
Bits
.
eight
.
value
,
8
)
def
test_powers_of_two_as_int
(
self
):
class
Bits
(
int
,
Enum
):
def
_generate_next_value_
(
name
,
start
,
count
,
last_value
):
return
2
**
count
one
two
four
eight
self
.
assertEqual
(
Bits
.
one
,
1
)
self
.
assertEqual
(
Bits
.
two
,
2
)
self
.
assertEqual
(
Bits
.
four
,
4
)
self
.
assertEqual
(
Bits
.
eight
,
8
)
class
TestUnique
(
unittest
.
TestCase
):
...
...
Misc/NEWS
View file @
73fc586d
...
...
@@ -78,6 +78,8 @@ Library
- Issue 27512: Fix a segfault when os.fspath() called a an __fspath__() method
that raised an exception. Patch by Xiang Zhang.
- Issue 26988: Add AutoEnum.
Tests
-----
...
...
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