Issue26988: remove AutoEnum

This commit is contained in:
Ethan Furman 2016-08-20 00:00:52 -07:00
parent 1cce732fcf
commit 332dbc7325
4 changed files with 77 additions and 663 deletions

View File

@ -37,13 +37,6 @@ one decorator, :func:`unique`.
Base class for creating enumerated constants that are also Base class for creating enumerated constants that are also
subclasses of :class:`int`. 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 .. function:: unique
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.
@ -54,14 +47,14 @@ Creating an Enum
Enumerations are created using the :keyword:`class` syntax, which makes them Enumerations are created using the :keyword:`class` syntax, which makes them
easy to read and write. An alternative creation method is described in easy to read and write. An alternative creation method is described in
`Functional API`_. To define a simple enumeration, subclass :class:`AutoEnum` `Functional API`_. To define an enumeration, subclass :class:`Enum` as
as follows:: follows::
>>> from enum import AutoEnum >>> from enum import Enum
>>> class Color(AutoEnum): >>> class Color(Enum):
... red ... red = 1
... green ... green = 2
... blue ... blue = 3
... ...
.. note:: Nomenclature .. note:: Nomenclature
@ -79,33 +72,6 @@ as follows::
are not normal Python classes. See `How are Enums different?`_ for are not normal Python classes. See `How are Enums different?`_ for
more details. 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:: Enumeration members have human readable string representations::
>>> print(Color.red) >>> print(Color.red)
@ -269,11 +235,7 @@ aliases::
The ``__members__`` attribute can be used for detailed programmatic access to The ``__members__`` attribute can be used for detailed programmatic access to
the enumeration members. For example, finding all the aliases:: 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'] ['alias_for_square']
@ -295,7 +257,7 @@ members are not integers (but see `IntEnum`_ below)::
>>> Color.red < Color.blue >>> Color.red < Color.blue
Traceback (most recent call last): Traceback (most recent call last):
File "<stdin>", line 1, in <module> 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:: 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 The examples above use integers for enumeration values. Using integers is
short and handy (and provided by default by :class:`AutoEnum` and the short and handy (and provided by default by the `Functional API`_), but not
`Functional API`_), but not strictly enforced. In the vast majority of strictly enforced. In the vast majority of use-cases, one doesn't care what
use-cases, one doesn't care what the actual value of an enumeration is. the actual value of an enumeration is. But if the value *is* important,
But if the value *is* important, enumerations can have arbitrary values. enumerations can have arbitrary values.
Enumerations are Python classes, and can have methods and special methods as Enumerations are Python classes, and can have methods and special methods as
usual. If we have this enumeration:: usual. If we have this enumeration::
@ -431,21 +393,17 @@ The :class:`Enum` class is callable, providing the following functional API::
>>> list(Animal) >>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>] [<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 The second argument is the *source* of enumeration member names. It can be a
enumeration; 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
- the second argument is the *source* of enumeration member names. It can be a values. The last two options enable assigning arbitrary values to
whitespace-separated string of names, a sequence of names, a sequence of enumerations; the others auto-assign increasing integers starting with 1 (use
2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to the ``start`` parameter to specify a different starting value). A
values; 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): >>> class Animal(Enum):
... ant = 1 ... ant = 1
@ -461,7 +419,7 @@ to ``True``.
Pickling enums created with the functional API can be tricky as frame stack 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 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 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:: The solution is to specify the module name explicitly as follows::
>>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__) >>> Animal = Enum('Animal', 'ant bee cat dog', module=__name__)
@ -481,15 +439,7 @@ SomeData in the global scope::
The complete signature is:: The complete signature is::
Enum( Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
value='NewEnumName',
names=<...>,
*,
module='...',
qualname='...',
type=<mixed-in class>,
start=1,
)
:value: What the new Enum class will record as its name. :value: What the new Enum class will record as its name.
@ -525,41 +475,10 @@ The complete signature is::
Derived Enumerations 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 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; :class:`int`. Members of an :class:`IntEnum` can be compared to integers;
by extension, integer enumerations of different types can also be compared by extension, integer enumerations of different types can also be compared
to each other:: 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)] >>> [i for i in range(Shape.square)]
[0, 1] [0, 1]
For the vast majority of code, :class:`Enum` and :class:`AutoEnum` are strongly For the vast majority of code, :class:`Enum` is strongly recommended,
recommended, since :class:`IntEnum` breaks some semantic promises of an since :class:`IntEnum` breaks some semantic promises of an enumeration (by
enumeration (by being comparable to integers, and thus by transitivity to other being comparable to integers, and thus by transitivity to other
unrelated ``IntEnum`` enumerations). It should be used only in special cases unrelated enumerations). It should be used only in special cases where
where there's no other choice; for example, when integer constants are replaced there's no other choice; for example, when integer constants are
with enumerations and backwards compatibility is required with code that still replaced with enumerations and backwards compatibility is required with code
expects integers. that still expects integers.
Others Others
^^^^^^ ^^^^^^
@ -620,9 +540,7 @@ simple to implement independently::
pass pass
This demonstrates how similar derived enumerations can be defined; for example This demonstrates how similar derived enumerations can be defined; for example
an :class:`AutoIntEnum` that mixes in :class:`int` with :class:`AutoEnum` a :class:`StrEnum` that mixes in :class:`str` instead of :class:`int`.
to get members that are :class:`int` (but keep in mind the warnings for
:class:`IntEnum`).
Some rules: Some rules:
@ -649,35 +567,31 @@ Some rules:
Interesting examples Interesting examples
-------------------- --------------------
While :class:`Enum`, :class:`AutoEnum`, and :class:`IntEnum` are expected While :class:`Enum` and :class:`IntEnum` are expected to cover the majority of
to cover the majority of use-cases, they cannot cover them all. Here are use-cases, they cannot cover them all. Here are recipes for some different
recipes for some different types of enumerations that can be used directly, types of enumerations that can be used directly, or as examples for creating
or as examples for creating one's own. one's own.
AutoDocEnum AutoNumber
^^^^^^^^^^^ ^^^^^^^^^^
Automatically numbers the members, and uses the given value as the Avoids having to specify the value for each enumeration member::
:attr:`__doc__` string::
>>> class AutoDocEnum(Enum): >>> class AutoNumber(Enum):
... def __new__(cls, doc): ... def __new__(cls):
... value = len(cls.__members__) + 1 ... value = len(cls.__members__) + 1
... obj = object.__new__(cls) ... obj = object.__new__(cls)
... obj._value_ = value ... obj._value_ = value
... obj.__doc__ = doc
... return obj ... return obj
... ...
>>> class Color(AutoDocEnum): >>> class Color(AutoNumber):
... red = 'stop' ... red = ()
... green = 'go' ... green = ()
... blue = 'what?' ... blue = ()
... ...
>>> Color.green.value == 2 >>> Color.green.value == 2
True True
>>> Color.green.__doc__
'go'
.. note:: .. 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 members; it is then replaced by Enum's :meth:`__new__` which is used after
class creation for lookup of existing members. 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 OrderedEnum
^^^^^^^^^^^ ^^^^^^^^^^^
@ -834,63 +731,10 @@ member instances.
Finer Points Finer Points
^^^^^^^^^^^^ ^^^^^^^^^^^^
Enum class signature :class:`Enum` members are instances of an :class:`Enum` class, and even
~~~~~~~~~~~~~~~~~~~~ though they are accessible as `EnumClass.member`, they should not be accessed
::
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 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): >>> class FieldTypes(Enum):
... name = 0 ... name = 0
@ -904,24 +748,18 @@ besides the ``Enum`` member you are looking for::
.. versionchanged:: 3.5 .. 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 :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 Enum's boolean evaluation depend on the member's value add the following to
your class:: your class::
def __bool__(self): def __bool__(self):
return bool(self.value) return bool(self.value)
The :attr:`__members__` attribute is only available on the class.
Enum classes with methods If you give your :class:`Enum` subclass extra methods, like the `Planet`_
~~~~~~~~~~~~~~~~~~~~~~~~~
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, class above, those methods will show up in a :func:`dir` of the member,
but not of the class:: 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__'] ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH) >>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value'] ['__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.

View File

@ -8,9 +8,7 @@ except ImportError:
from collections import OrderedDict from collections import OrderedDict
__all__ = [ __all__ = ['EnumMeta', 'Enum', 'IntEnum', 'unique']
'EnumMeta', 'Enum', 'IntEnum', 'AutoEnum', 'unique',
]
def _is_descriptor(obj): def _is_descriptor(obj):
@ -54,30 +52,7 @@ class _EnumDict(dict):
""" """
def __init__(self): def __init__(self):
super().__init__() super().__init__()
# list of enum members
self._member_names = [] 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): def __setitem__(self, key, value):
"""Changes anything not dundered or not a descriptor. """Changes anything not dundered or not a descriptor.
@ -89,55 +64,19 @@ class _EnumDict(dict):
""" """
if _is_sunder(key): if _is_sunder(key):
if key not in ('_settings_', '_order_', '_ignore_', '_start_', '_generate_next_value_'): raise ValueError('_names_ are reserved for future Enum use')
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): elif _is_dunder(key):
if key == '__order__': pass
key = '_order_'
if _is_descriptor(value):
self._locked = True
elif key in self._member_names: elif key in self._member_names:
# descriptor overwriting an enum? # descriptor overwriting an enum?
raise TypeError('Attempted to reuse key: %r' % key) raise TypeError('Attempted to reuse key: %r' % key)
elif key in self._ignore:
pass
elif not _is_descriptor(value): elif not _is_descriptor(value):
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('Key already defined as: %r' % self[key])
self._member_names.append(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) 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 # 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` # This is also why there are checks in EnumMeta like `if Enum is not None`
Enum = None Enum = None
_ignore_sentinel = object()
class EnumMeta(type): class EnumMeta(type):
"""Metaclass for Enum""" """Metaclass for Enum"""
@classmethod @classmethod
def __prepare__(metacls, cls, bases, start=None, ignore=_ignore_sentinel): def __prepare__(metacls, cls, bases):
# create the namespace dict return _EnumDict()
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): def __new__(metacls, cls, bases, classdict):
super(EnumMeta, cls).__init__(*args)
def __new__(metacls, cls, bases, classdict, **kwds):
# an Enum class is final once enumeration items have been defined; it # 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 # cannot be mixed with other types (int, float, etc.) if it has an
# inherited __new__ unless a new __new__ is defined (or the resulting # 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 # save enum items into separate mapping so they don't get baked into
# the new class # 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: for name in classdict._member_names:
del classdict[name] 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?) # check for illegal enum names (any others?)
invalid_names = set(enum_members) & {'mro', } invalid_names = set(members) & {'mro', }
if invalid_names: if invalid_names:
raise ValueError('Invalid enum member name: {0}'.format( raise ValueError('Invalid enum member name: {0}'.format(
','.join(invalid_names))) ','.join(invalid_names)))
@ -241,7 +151,7 @@ class EnumMeta(type):
# a custom __new__ is doing something funky with the values -- such as # a custom __new__ is doing something funky with the values -- such as
# auto-numbering ;) # auto-numbering ;)
for member_name in classdict._member_names: for member_name in classdict._member_names:
value = enum_members[member_name] value = members[member_name]
if not isinstance(value, tuple): if not isinstance(value, tuple):
args = (value, ) args = (value, )
else: else:
@ -255,10 +165,7 @@ class EnumMeta(type):
else: else:
enum_member = __new__(enum_class, *args) enum_member = __new__(enum_class, *args)
if not hasattr(enum_member, '_value_'): if not hasattr(enum_member, '_value_'):
if member_type is object: enum_member._value_ = member_type(*args)
enum_member._value_ = value
else:
enum_member._value_ = member_type(*args)
value = enum_member._value_ value = enum_member._value_
enum_member._name_ = member_name enum_member._name_ = member_name
enum_member.__objclass__ = enum_class enum_member.__objclass__ = enum_class
@ -665,22 +572,6 @@ class IntEnum(int, Enum):
def _reduce_ex_by_name(self, proto): def _reduce_ex_by_name(self, proto):
return self.name 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): def unique(enumeration):
"""Class decorator for enumerations ensuring unique member values.""" """Class decorator for enumerations ensuring unique member values."""
duplicates = [] duplicates = []

View File

@ -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 EnumMeta, Enum, IntEnum, AutoEnum, unique from enum import Enum, IntEnum, EnumMeta, unique
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
@ -1570,328 +1570,6 @@ 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_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): class TestUnique(unittest.TestCase):

View File

@ -179,8 +179,6 @@ Library
- Issue #27512: Fix a segfault when os.fspath() called a an __fspath__() method - Issue #27512: Fix a segfault when os.fspath() called a an __fspath__() method
that raised an exception. Patch by Xiang Zhang. that raised an exception. Patch by Xiang Zhang.
- Issue #26988: Add AutoEnum.
Tests Tests
----- -----