Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f24bb35

Browse files
committed
closes issue18042 -- a unique decorator is added to enum.py
The docs also clarify the 'Interesting Example' duplicate-free enum is for demonstration purposes.
1 parent d85032e commit f24bb35

3 files changed

Lines changed: 115 additions & 29 deletions

File tree

Doc/library/enum.rst

Lines changed: 66 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ values. Within an enumeration, the members can be compared by identity, and
1818
the enumeration itself can be iterated over.
1919

2020
This module defines two enumeration classes that can be used to define unique
21-
sets of names and values: :class:`Enum` and :class:`IntEnum`.
21+
sets of names and values: :class:`Enum` and :class:`IntEnum`. It also defines
22+
one decorator, :func:`unique`, that ensures only unique member values are
23+
present in an enumeration.
24+
2225

2326
Creating an Enum
2427
----------------
@@ -146,6 +149,35 @@ return A::
146149
>>> Shape(2)
147150
<Shape.square: 2>
148151

152+
153+
Ensuring unique enumeration values
154+
==================================
155+
156+
By default, enumerations allow multiple names as aliases for the same value.
157+
When this behavior isn't desired, the following decorator can be used to
158+
ensure each value is used only once in the enumeration:
159+
160+
.. decorator:: unique
161+
162+
A :keyword:`class` decorator specifically for enumerations. It searches an
163+
enumeration's :attr:`__members__` gathering any aliases it finds; if any are
164+
found :exc:`ValueError` is raised with the details::
165+
166+
>>> from enum import Enum, unique
167+
>>> @unique
168+
... class Mistake(Enum):
169+
... one = 1
170+
... two = 2
171+
... three = 3
172+
... four = 3
173+
Traceback (most recent call last):
174+
...
175+
ValueError: duplicate values found in <enum 'Mistake'>: four -> three
176+
177+
178+
Iteration
179+
=========
180+
149181
Iterating over the members of an enum does not provide the aliases::
150182

151183
>>> list(Shape)
@@ -169,6 +201,7 @@ the enumeration members. For example, finding all the aliases::
169201
>>> [name for name, member in Shape.__members__.items() if member.name != name]
170202
['alias_for_square']
171203

204+
172205
Comparisons
173206
-----------
174207

@@ -462,32 +495,6 @@ Avoids having to specify the value for each enumeration member::
462495
True
463496

464497

465-
UniqueEnum
466-
----------
467-
468-
Raises an error if a duplicate member name is found instead of creating an
469-
alias::
470-
471-
>>> class UniqueEnum(Enum):
472-
... def __init__(self, *args):
473-
... cls = self.__class__
474-
... if any(self.value == e.value for e in cls):
475-
... a = self.name
476-
... e = cls(self.value).name
477-
... raise ValueError(
478-
... "aliases not allowed in UniqueEnum: %r --> %r"
479-
... % (a, e))
480-
...
481-
>>> class Color(UniqueEnum):
482-
... red = 1
483-
... green = 2
484-
... blue = 3
485-
... grene = 2
486-
Traceback (most recent call last):
487-
...
488-
ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green'
489-
490-
491498
OrderedEnum
492499
-----------
493500

@@ -524,6 +531,38 @@ enumerations)::
524531
True
525532

526533

534+
DuplicateFreeEnum
535+
-----------------
536+
537+
Raises an error if a duplicate member name is found instead of creating an
538+
alias::
539+
540+
>>> class DuplicateFreeEnum(Enum):
541+
... def __init__(self, *args):
542+
... cls = self.__class__
543+
... if any(self.value == e.value for e in cls):
544+
... a = self.name
545+
... e = cls(self.value).name
546+
... raise ValueError(
547+
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
548+
... % (a, e))
549+
...
550+
>>> class Color(DuplicateFreeEnum):
551+
... red = 1
552+
... green = 2
553+
... blue = 3
554+
... grene = 2
555+
Traceback (most recent call last):
556+
...
557+
ValueError: aliases not allowed in DuplicateFreeEnum: 'grene' --> 'green'
558+
559+
.. note::
560+
561+
This is a useful example for subclassing Enum to add or change other
562+
behaviors as well as disallowing aliases. If the only change desired is
563+
no aliases allowed the :func:`unique` decorator can be used instead.
564+
565+
527566
Planet
528567
------
529568

Lib/enum.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections import OrderedDict
55
from types import MappingProxyType
66

7-
__all__ = ['Enum', 'IntEnum']
7+
__all__ = ['Enum', 'IntEnum', 'unique']
88

99

1010
class _RouteClassAttributeToGetattr:
@@ -463,3 +463,17 @@ def value(self):
463463

464464
class IntEnum(int, Enum):
465465
"""Enum where members are also (and must be) ints"""
466+
467+
468+
def unique(enumeration):
469+
"""Class decorator for enumerations ensuring unique member values."""
470+
duplicates = []
471+
for name, member in enumeration.__members__.items():
472+
if name != member.name:
473+
duplicates.append((name, member.name))
474+
if duplicates:
475+
alias_details = ', '.join(
476+
["%s -> %s" % (alias, name) for (alias, name) in duplicates])
477+
raise ValueError('duplicate values found in %r: %s' %
478+
(enumeration, alias_details))
479+
return enumeration

Lib/test/test_enum.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import unittest
33
from collections import OrderedDict
44
from pickle import dumps, loads, PicklingError
5-
from enum import Enum, IntEnum
5+
from enum import Enum, IntEnum, unique
66

77
# for pickle tests
88
try:
@@ -917,5 +917,38 @@ def surface_gravity(self):
917917
self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6))
918918

919919

920+
class TestUnique(unittest.TestCase):
921+
922+
def test_unique_clean(self):
923+
@unique
924+
class Clean(Enum):
925+
one = 1
926+
two = 'dos'
927+
tres = 4.0
928+
@unique
929+
class Cleaner(IntEnum):
930+
single = 1
931+
double = 2
932+
triple = 3
933+
934+
def test_unique_dirty(self):
935+
with self.assertRaisesRegex(ValueError, 'tres.*one'):
936+
@unique
937+
class Dirty(Enum):
938+
one = 1
939+
two = 'dos'
940+
tres = 1
941+
with self.assertRaisesRegex(
942+
ValueError,
943+
'double.*single.*turkey.*triple',
944+
):
945+
@unique
946+
class Dirtier(IntEnum):
947+
single = 1
948+
double = 1
949+
triple = 3
950+
turkey = 3
951+
952+
920953
if __name__ == '__main__':
921954
unittest.main()

0 commit comments

Comments
 (0)