diff -r 974d4844d5a7 Doc/library/enum.rst --- a/Doc/library/enum.rst Wed Jun 19 09:01:58 2013 -0700 +++ b/Doc/library/enum.rst Fri Jun 28 01:47:07 2013 -0700 @@ -11,21 +11,24 @@ **Source code:** :source:`Lib/enum.py` ---------------- An enumeration is a set of symbolic names (members) bound to unique, constant 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 names are present in an +enumeration. + Creating an Enum ---------------- Enumerations are created using the :keyword:`class` syntax, which makes them easy to read and write. An alternative creation method is described in `Functional API`_. To define an enumeration, subclass :class:`Enum` as follows:: >>> from enum import Enum @@ -413,20 +416,28 @@ 1. When subclassing :class:`Enum`, mix-i 2. While :class:`Enum` can have members of any type, once you mix in an additional type, all the members must have values of that type, e.g. :class:`int` above. This restriction does not apply to mix-ins which only add methods and don't specify another data type such as :class:`int` or :class:`str`. 3. When another data type is mixed in, the :attr:`value` attribute is *not the same* as the enum member itself, although it is equivalant and will compare equal. +Decorators +========== + +Only one decorator is currenly defined: :func:`unique`. When given a class +it processes :attr:`__members__` gathering any aliases it finds; if it finds +any it raises :exc:`ValueError` with the aliases. + + Interesting examples ==================== While :class:`Enum` and :class:`IntEnum` are expected to cover the majority of use-cases, they cannot cover them all. Here are recipes for some different types of enumerations that can be used directly, or as examples for creating one's own. AutoNumber diff -r 974d4844d5a7 Lib/enum.py --- a/Lib/enum.py Wed Jun 19 09:01:58 2013 -0700 +++ b/Lib/enum.py Fri Jun 28 01:47:07 2013 -0700 @@ -1,17 +1,17 @@ """Python Enumerations""" import sys from collections import OrderedDict from types import MappingProxyType -__all__ = ['Enum', 'IntEnum'] +__all__ = ['Enum', 'IntEnum', 'unique'] class _RouteClassAttributeToGetattr: """Route attribute access on a class to __getattr__. This is a descriptor, used to define attributes that act differently when accessed through an instance and through a class. Instance access remains normal, but access to an attribute through a class will be routed to the class's __getattr__ method; this is done by raising AttributeError. @@ -456,10 +456,21 @@ class Enum(metaclass=EnumMeta): def name(self): return self._name @_RouteClassAttributeToGetattr def value(self): return self._value class IntEnum(int, Enum): """Enum where members are also (and must be) ints""" + + +def unique(enumeration): + """Ensures that only unique members exist in enumeration.""" + duplicates = [] + for name, member in enumeration.__members__.items(): + if name != member.name: + duplicates.append((name, member.name)) + if duplicates: + raise ValueError('duplicate names found: %s' % ', '.join(["%s -> %s" % (alias, name) for (alias, name) in duplicates])) + return enumeration diff -r 974d4844d5a7 Lib/test/test_enum.py --- a/Lib/test/test_enum.py Wed Jun 19 09:01:58 2013 -0700 +++ b/Lib/test/test_enum.py Fri Jun 28 01:47:07 2013 -0700 @@ -1,15 +1,15 @@ 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: class Stooges(Enum): LARRY = 1 CURLY = 2 MOE = 3 except Exception as exc: Stooges = exc @@ -910,12 +910,46 @@ class TestEnum(unittest.TestCase): self.radius = radius # in meters @property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) G = 6.67300E-11 return G * self.mass / (self.radius * self.radius) self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80) self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6)) +class TestDecorators(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.assertRaises(ValueError): + @unique + class Dirty(Enum): + one = 1 + two = 'dos' + tres = 1 + try: + @unique + class Dirtier(IntEnum): + single = 1 + double = 1 + triple = 3 + turkey = 3 + except ValueError as exc: + message = exc.args[0] + self.assertTrue('double' in message, "double not in %s" % message) + self.assertTrue('turkey' in message, "turkey not in %s" % message) + + if __name__ == '__main__': unittest.main()