summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEthan Furman <ethan@stoneleaf.us>2013-07-18 17:05:39 -0700
committerEthan Furman <ethan@stoneleaf.us>2013-07-18 17:05:39 -0700
commitf24bb35a69d18a05047399eadc63b4be092aee71 (patch)
treed2cb70aa9a38166dc38bf54a056f238f43caf747
parentd85032e25d4f9864ecc694a02a6678b4e6069739 (diff)
downloadcpython-git-f24bb35a69d18a05047399eadc63b4be092aee71.tar.gz
closes issue18042 -- a `unique` decorator is added to enum.py
The docs also clarify the 'Interesting Example' duplicate-free enum is for demonstration purposes.
-rw-r--r--Doc/library/enum.rst93
-rw-r--r--Lib/enum.py16
-rw-r--r--Lib/test/test_enum.py35
3 files changed, 115 insertions, 29 deletions
diff --git a/Doc/library/enum.rst b/Doc/library/enum.rst
index b919bdc839..1e464d7361 100644
--- a/Doc/library/enum.rst
+++ b/Doc/library/enum.rst
@@ -18,7 +18,10 @@ values. Within an enumeration, the members can be compared by identity, and
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 @@ return A::
>>> 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 @@ the enumeration members. For example, finding all the aliases::
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['alias_for_square']
+
Comparisons
-----------
@@ -462,32 +495,6 @@ Avoids having to specify the value for each enumeration member::
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 @@ enumerations)::
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
index 775489bf95..38d95c5b4c 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -4,7 +4,7 @@ import sys
from collections import OrderedDict
from types import MappingProxyType
-__all__ = ['Enum', 'IntEnum']
+__all__ = ['Enum', 'IntEnum', 'unique']
class _RouteClassAttributeToGetattr:
@@ -463,3 +463,17 @@ class Enum(metaclass=EnumMeta):
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
index 75b2656862..2b87c562da 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -2,7 +2,7 @@ import enum
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 @@ class TestEnum(unittest.TestCase):
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()