mirror of
https://github.com/python/cpython.git
synced 2025-10-07 15:42:02 +00:00
closes issue18042 -- a unique
decorator is added to enum.py
The docs also clarify the 'Interesting Example' duplicate-free enum is for demonstration purposes.
This commit is contained in:
parent
d85032e25d
commit
f24bb35a69
3 changed files with 115 additions and 29 deletions
|
@ -18,7 +18,10 @@ values. Within an enumeration, the members can be compared by identity, and
|
||||||
the enumeration itself can be iterated over.
|
the enumeration itself can be iterated over.
|
||||||
|
|
||||||
This module defines two enumeration classes that can be used to define unique
|
This module defines two enumeration classes that can be used to define unique
|
||||||
sets of names and values: :class:`Enum` and :class:`IntEnum`.
|
sets of names and values: :class:`Enum` and :class:`IntEnum`. It also defines
|
||||||
|
one decorator, :func:`unique`, that ensures only unique member values are
|
||||||
|
present in an enumeration.
|
||||||
|
|
||||||
|
|
||||||
Creating an Enum
|
Creating an Enum
|
||||||
----------------
|
----------------
|
||||||
|
@ -146,6 +149,35 @@ return A::
|
||||||
>>> Shape(2)
|
>>> Shape(2)
|
||||||
<Shape.square: 2>
|
<Shape.square: 2>
|
||||||
|
|
||||||
|
|
||||||
|
Ensuring unique enumeration values
|
||||||
|
==================================
|
||||||
|
|
||||||
|
By default, enumerations allow multiple names as aliases for the same value.
|
||||||
|
When this behavior isn't desired, the following decorator can be used to
|
||||||
|
ensure each value is used only once in the enumeration:
|
||||||
|
|
||||||
|
.. decorator:: unique
|
||||||
|
|
||||||
|
A :keyword:`class` decorator specifically for enumerations. It searches an
|
||||||
|
enumeration's :attr:`__members__` gathering any aliases it finds; if any are
|
||||||
|
found :exc:`ValueError` is raised with the details::
|
||||||
|
|
||||||
|
>>> from enum import Enum, unique
|
||||||
|
>>> @unique
|
||||||
|
... class Mistake(Enum):
|
||||||
|
... one = 1
|
||||||
|
... two = 2
|
||||||
|
... three = 3
|
||||||
|
... four = 3
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
|
||||||
|
|
||||||
|
|
||||||
|
Iteration
|
||||||
|
=========
|
||||||
|
|
||||||
Iterating over the members of an enum does not provide the aliases::
|
Iterating over the members of an enum does not provide the aliases::
|
||||||
|
|
||||||
>>> list(Shape)
|
>>> list(Shape)
|
||||||
|
@ -169,6 +201,7 @@ 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']
|
||||||
|
|
||||||
|
|
||||||
Comparisons
|
Comparisons
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -462,32 +495,6 @@ Avoids having to specify the value for each enumeration member::
|
||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
UniqueEnum
|
|
||||||
----------
|
|
||||||
|
|
||||||
Raises an error if a duplicate member name is found instead of creating an
|
|
||||||
alias::
|
|
||||||
|
|
||||||
>>> class UniqueEnum(Enum):
|
|
||||||
... def __init__(self, *args):
|
|
||||||
... cls = self.__class__
|
|
||||||
... if any(self.value == e.value for e in cls):
|
|
||||||
... a = self.name
|
|
||||||
... e = cls(self.value).name
|
|
||||||
... raise ValueError(
|
|
||||||
... "aliases not allowed in UniqueEnum: %r --> %r"
|
|
||||||
... % (a, e))
|
|
||||||
...
|
|
||||||
>>> class Color(UniqueEnum):
|
|
||||||
... red = 1
|
|
||||||
... green = 2
|
|
||||||
... blue = 3
|
|
||||||
... grene = 2
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green'
|
|
||||||
|
|
||||||
|
|
||||||
OrderedEnum
|
OrderedEnum
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -524,6 +531,38 @@ enumerations)::
|
||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
|
DuplicateFreeEnum
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Raises an error if a duplicate member name is found instead of creating an
|
||||||
|
alias::
|
||||||
|
|
||||||
|
>>> class DuplicateFreeEnum(Enum):
|
||||||
|
... def __init__(self, *args):
|
||||||
|
... cls = self.__class__
|
||||||
|
... if any(self.value == e.value for e in cls):
|
||||||
|
... a = self.name
|
||||||
|
... e = cls(self.value).name
|
||||||
|
... raise ValueError(
|
||||||
|
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
|
||||||
|
... % (a, e))
|
||||||
|
...
|
||||||
|
>>> class Color(DuplicateFreeEnum):
|
||||||
|
... red = 1
|
||||||
|
... green = 2
|
||||||
|
... blue = 3
|
||||||
|
... grene = 2
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValueError: aliases not allowed in DuplicateFreeEnum: 'grene' --> 'green'
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This is a useful example for subclassing Enum to add or change other
|
||||||
|
behaviors as well as disallowing aliases. If the only change desired is
|
||||||
|
no aliases allowed the :func:`unique` decorator can be used instead.
|
||||||
|
|
||||||
|
|
||||||
Planet
|
Planet
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|
16
Lib/enum.py
16
Lib/enum.py
|
@ -4,7 +4,7 @@ import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
|
||||||
__all__ = ['Enum', 'IntEnum']
|
__all__ = ['Enum', 'IntEnum', 'unique']
|
||||||
|
|
||||||
|
|
||||||
class _RouteClassAttributeToGetattr:
|
class _RouteClassAttributeToGetattr:
|
||||||
|
@ -463,3 +463,17 @@ class Enum(metaclass=EnumMeta):
|
||||||
|
|
||||||
class IntEnum(int, Enum):
|
class IntEnum(int, Enum):
|
||||||
"""Enum where members are also (and must be) ints"""
|
"""Enum where members are also (and must be) ints"""
|
||||||
|
|
||||||
|
|
||||||
|
def unique(enumeration):
|
||||||
|
"""Class decorator for enumerations ensuring unique member values."""
|
||||||
|
duplicates = []
|
||||||
|
for name, member in enumeration.__members__.items():
|
||||||
|
if name != member.name:
|
||||||
|
duplicates.append((name, member.name))
|
||||||
|
if duplicates:
|
||||||
|
alias_details = ', '.join(
|
||||||
|
["%s -> %s" % (alias, name) for (alias, name) in duplicates])
|
||||||
|
raise ValueError('duplicate values found in %r: %s' %
|
||||||
|
(enumeration, alias_details))
|
||||||
|
return enumeration
|
||||||
|
|
|
@ -2,7 +2,7 @@ import enum
|
||||||
import unittest
|
import unittest
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pickle import dumps, loads, PicklingError
|
from pickle import dumps, loads, PicklingError
|
||||||
from enum import Enum, IntEnum
|
from enum import Enum, IntEnum, unique
|
||||||
|
|
||||||
# for pickle tests
|
# for pickle tests
|
||||||
try:
|
try:
|
||||||
|
@ -917,5 +917,38 @@ class TestEnum(unittest.TestCase):
|
||||||
self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
|
self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnique(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_unique_clean(self):
|
||||||
|
@unique
|
||||||
|
class Clean(Enum):
|
||||||
|
one = 1
|
||||||
|
two = 'dos'
|
||||||
|
tres = 4.0
|
||||||
|
@unique
|
||||||
|
class Cleaner(IntEnum):
|
||||||
|
single = 1
|
||||||
|
double = 2
|
||||||
|
triple = 3
|
||||||
|
|
||||||
|
def test_unique_dirty(self):
|
||||||
|
with self.assertRaisesRegex(ValueError, 'tres.*one'):
|
||||||
|
@unique
|
||||||
|
class Dirty(Enum):
|
||||||
|
one = 1
|
||||||
|
two = 'dos'
|
||||||
|
tres = 1
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
ValueError,
|
||||||
|
'double.*single.*turkey.*triple',
|
||||||
|
):
|
||||||
|
@unique
|
||||||
|
class Dirtier(IntEnum):
|
||||||
|
single = 1
|
||||||
|
double = 1
|
||||||
|
triple = 3
|
||||||
|
turkey = 3
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue