[Python-checkins] cpython: closes issue18042 -- a `unique` decorator is added to enum.py

ethan.furman python-checkins at python.org
Fri Jul 19 02:05:56 CEST 2013


http://hg.python.org/cpython/rev/2079a517193b
changeset:   84713:2079a517193b
user:        Ethan Furman <ethan at stoneleaf.us>
date:        Thu Jul 18 17:05:39 2013 -0700
summary:
  closes issue18042 -- a `unique` decorator is added to enum.py

The docs also clarify the 'Interesting Example' duplicate-free enum is for
demonstration purposes.

files:
  Doc/library/enum.rst  |  93 ++++++++++++++++++++++---------
  Lib/enum.py           |  16 +++++-
  Lib/test/test_enum.py |  35 +++++++++++-
  3 files changed, 115 insertions(+), 29 deletions(-)


diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -18,7 +18,10 @@
 the enumeration itself can be iterated over.
 
 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
 ----------------
@@ -146,6 +149,35 @@
     >>> Shape(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::
 
     >>> list(Shape)
@@ -169,6 +201,7 @@
     >>> [name for name, member in Shape.__members__.items() if member.name != name]
     ['alias_for_square']
 
+
 Comparisons
 -----------
 
@@ -462,32 +495,6 @@
     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
 -----------
 
@@ -524,6 +531,38 @@
     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
 ------
 
diff --git a/Lib/enum.py b/Lib/enum.py
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -4,7 +4,7 @@
 from collections import OrderedDict
 from types import MappingProxyType
 
-__all__ = ['Enum', 'IntEnum']
+__all__ = ['Enum', 'IntEnum', 'unique']
 
 
 class _RouteClassAttributeToGetattr:
@@ -463,3 +463,17 @@
 
 class IntEnum(int, Enum):
     """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
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -2,7 +2,7 @@
 import unittest
 from collections import OrderedDict
 from pickle import dumps, loads, PicklingError
-from enum import Enum, IntEnum
+from enum import Enum, IntEnum, unique
 
 # for pickle tests
 try:
@@ -917,5 +917,38 @@
         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__':
     unittest.main()

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list