Issue26988: remove AutoEnum
This commit is contained in:
parent
1cce732fcf
commit
332dbc7325
|
@ -37,13 +37,6 @@ 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.
|
||||
|
@ -54,14 +47,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 simple enumeration, subclass :class:`AutoEnum`
|
||||
as follows::
|
||||
`Functional API`_. To define an enumeration, subclass :class:`Enum` as
|
||||
follows::
|
||||
|
||||
>>> from enum import AutoEnum
|
||||
>>> class Color(AutoEnum):
|
||||
... red
|
||||
... green
|
||||
... blue
|
||||
>>> from enum import Enum
|
||||
>>> class Color(Enum):
|
||||
... red = 1
|
||||
... green = 2
|
||||
... blue = 3
|
||||
...
|
||||
|
||||
.. note:: Nomenclature
|
||||
|
@ -79,33 +72,6 @@ as 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)
|
||||
|
@ -269,11 +235,7 @@ 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']
|
||||
|
||||
|
||||
|
@ -295,7 +257,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: '<' not supported between instances of 'Color' and 'Color'
|
||||
TypeError: unorderable types: Color() < Color()
|
||||
|
||||
Equality comparisons are defined though::
|
||||
|
||||
|
@ -318,10 +280,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 :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.
|
||||
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.
|
||||
|
||||
Enumerations are Python classes, and can have methods and special methods as
|
||||
usual. If we have this enumeration::
|
||||
|
@ -431,21 +393,17 @@ 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 semantics of this API resemble :class:`~collections.namedtuple`. The first
|
||||
argument of the call to :class:`Enum` is the name of the enumeration.
|
||||
|
||||
- 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 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 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::
|
||||
|
||||
>>> class Animal(Enum):
|
||||
... ant = 1
|
||||
|
@ -461,7 +419,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 a separate module, and also may not work on IronPython or Jython).
|
||||
function in 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__)
|
||||
|
@ -481,15 +439,7 @@ 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.
|
||||
|
||||
|
@ -525,41 +475,10 @@ 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
|
||||
^^^^^^^
|
||||
|
||||
Another variation of :class:`Enum` which is also a subclass of
|
||||
A variation of :class:`Enum` is provided 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::
|
||||
|
@ -602,13 +521,14 @@ 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` 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.
|
||||
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.
|
||||
|
||||
|
||||
Others
|
||||
^^^^^^
|
||||
|
@ -620,9 +540,7 @@ simple to implement independently::
|
|||
pass
|
||||
|
||||
This demonstrates how similar derived enumerations can be defined; for example
|
||||
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`).
|
||||
a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`.
|
||||
|
||||
Some rules:
|
||||
|
||||
|
@ -649,35 +567,31 @@ Some rules:
|
|||
Interesting examples
|
||||
--------------------
|
||||
|
||||
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,
|
||||
or as examples for creating one's own.
|
||||
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.
|
||||
|
||||
|
||||
AutoDocEnum
|
||||
^^^^^^^^^^^
|
||||
AutoNumber
|
||||
^^^^^^^^^^
|
||||
|
||||
Automatically numbers the members, and uses the given value as the
|
||||
:attr:`__doc__` string::
|
||||
Avoids having to specify the value for each enumeration member::
|
||||
|
||||
>>> class AutoDocEnum(Enum):
|
||||
... def __new__(cls, doc):
|
||||
>>> class AutoNumber(Enum):
|
||||
... def __new__(cls):
|
||||
... value = len(cls.__members__) + 1
|
||||
... obj = object.__new__(cls)
|
||||
... obj._value_ = value
|
||||
... obj.__doc__ = doc
|
||||
... return obj
|
||||
...
|
||||
>>> class Color(AutoDocEnum):
|
||||
... red = 'stop'
|
||||
... green = 'go'
|
||||
... blue = 'what?'
|
||||
>>> class Color(AutoNumber):
|
||||
... red = ()
|
||||
... green = ()
|
||||
... blue = ()
|
||||
...
|
||||
>>> Color.green.value == 2
|
||||
True
|
||||
>>> Color.green.__doc__
|
||||
'go'
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -685,23 +599,6 @@ Automatically numbers the members, and uses the given value as the
|
|||
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
|
||||
^^^^^^^^^^^
|
||||
|
@ -834,63 +731,10 @@ member instances.
|
|||
Finer Points
|
||||
^^^^^^^^^^^^
|
||||
|
||||
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
|
||||
:class:`Enum` members are instances of an :class:`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 ``Enum`` member you are looking for::
|
||||
besides the :class:`Enum` member you looking for::
|
||||
|
||||
>>> class FieldTypes(Enum):
|
||||
... name = 0
|
||||
|
@ -904,24 +748,18 @@ besides the ``Enum`` member you are looking for::
|
|||
|
||||
.. versionchanged:: 3.5
|
||||
|
||||
|
||||
Boolean value of ``Enum`` classes and members
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Enum classes that are mixed with non-Enum types (such as
|
||||
Boolean evaluation: 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 :data:`True`. To make your own
|
||||
type's rules; otherwise, all members evaluate as ``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.
|
||||
|
||||
Enum classes with methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you give your ``Enum`` subclass extra methods, like the `Planet`_
|
||||
If you give your :class:`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::
|
||||
|
||||
|
@ -929,3 +767,12 @@ 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.
|
||||
|
|
133
Lib/enum.py
133
Lib/enum.py
|
@ -8,9 +8,7 @@ except ImportError:
|
|||
from collections import OrderedDict
|
||||
|
||||
|
||||
__all__ = [
|
||||
'EnumMeta', 'Enum', 'IntEnum', 'AutoEnum', 'unique',
|
||||
]
|
||||
__all__ = ['EnumMeta', 'Enum', 'IntEnum', 'unique']
|
||||
|
||||
|
||||
def _is_descriptor(obj):
|
||||
|
@ -54,30 +52,7 @@ 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.
|
||||
|
@ -89,55 +64,19 @@ class _EnumDict(dict):
|
|||
|
||||
"""
|
||||
if _is_sunder(key):
|
||||
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
|
||||
raise ValueError('_names_ are reserved for future Enum use')
|
||||
elif _is_dunder(key):
|
||||
if key == '__order__':
|
||||
key = '_order_'
|
||||
if _is_descriptor(value):
|
||||
self._locked = True
|
||||
pass
|
||||
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('%r already defined as: %r' % (key, self[key]))
|
||||
raise TypeError('Key already defined as: %r' % 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
|
||||
|
@ -145,31 +84,14 @@ 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, 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 __prepare__(metacls, cls, bases):
|
||||
return _EnumDict()
|
||||
|
||||
def __init__(cls, *args , **kwds):
|
||||
super(EnumMeta, cls).__init__(*args)
|
||||
|
||||
def __new__(metacls, cls, bases, classdict, **kwds):
|
||||
def __new__(metacls, cls, bases, classdict):
|
||||
# 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
|
||||
|
@ -180,24 +102,12 @@ class EnumMeta(type):
|
|||
|
||||
# save enum items into separate mapping so they don't get baked into
|
||||
# the new class
|
||||
enum_members = {k: classdict[k] for k in classdict._member_names}
|
||||
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(enum_members) & {'mro', }
|
||||
invalid_names = set(members) & {'mro', }
|
||||
if invalid_names:
|
||||
raise ValueError('Invalid enum member name: {0}'.format(
|
||||
','.join(invalid_names)))
|
||||
|
@ -241,7 +151,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 = enum_members[member_name]
|
||||
value = members[member_name]
|
||||
if not isinstance(value, tuple):
|
||||
args = (value, )
|
||||
else:
|
||||
|
@ -255,10 +165,7 @@ class EnumMeta(type):
|
|||
else:
|
||||
enum_member = __new__(enum_class, *args)
|
||||
if not hasattr(enum_member, '_value_'):
|
||||
if member_type is object:
|
||||
enum_member._value_ = value
|
||||
else:
|
||||
enum_member._value_ = member_type(*args)
|
||||
enum_member._value_ = member_type(*args)
|
||||
value = enum_member._value_
|
||||
enum_member._name_ = member_name
|
||||
enum_member.__objclass__ = enum_class
|
||||
|
@ -665,22 +572,6 @@ 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 = []
|
||||
|
|
|
@ -3,7 +3,7 @@ import inspect
|
|||
import pydoc
|
||||
import unittest
|
||||
from collections import OrderedDict
|
||||
from enum import EnumMeta, Enum, IntEnum, AutoEnum, unique
|
||||
from enum import Enum, IntEnum, EnumMeta, unique
|
||||
from io import StringIO
|
||||
from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL
|
||||
from test import support
|
||||
|
@ -1570,328 +1570,6 @@ 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):
|
||||
|
||||
|
|
Loading…
Reference in New Issue