Recipe16.9.Simulating Enumerations in Python


Recipe 16.9. Simulating Enumerations in Python

Credit: Will Ware

Problem

You want to define an enumeration in the spirit of C's enum type.

Solution

Python's introspection facilities let you code a class that implements a version of enum, even though Python, as a language, does not support the enum construct:

class EnumException(Exception):     pass class Enumeration(object):     def _ _init_ _(self, name, enumList, valuesAreUnique=True):         self._ _doc_ _ = name         self.lookup = lookup = {  }         self.reverseLookup = reverseLookup = {  }         i = 0         for x in enumList:             if type(x) is tuple:                 try:                     x, i = x                 except ValueError:                     raise EnumException, "tuple doesn't have 2 items: %r" % (x,)             if type(x) is not str:                 raise EnumException, "enum name is not a string: %r" % (x,)             if type(i) is not int:                 raise EnumException, "enum value is not an integer: %r" % (i,)             if x in lookup:                 raise EnumException, "enum name is not unique: %r" % (x,)             if valuesAreUnique and i in reverseLookup:                 raise EnumException, "enum value %r not unique for %r" % (i, x)             lookup[x] = i             reverseLookup[i] = x             i = i + 1     def _ _getattr_ _(self, attr):         try: return self.lookup[attr]         except KeyError: raise AttributeError, attr     def whatis(self, value):         return self.reverseLookup[value]

Discussion

In the C language, enum lets you declare several named constants, typically with unique values (although you can also explicitly arrange for a value to be duplicated under two different names), without necessarily specifying the actual values (except when you want it to). Despite the similarity in naming, C's enum and this recipe's Enumeration class have little to do with the Python built-in enumerate generator, which is used to loop on (index, item) pairs given an iterablean entirely different issue!

Python has an accepted idiom that's fine for small numbers of constants:

A, B, C, D = range(4)

However, this idiom doesn't scale well to large numbers of constants and doesn't allow you to specify values for some constants while leaving others to be determined automatically. This recipe provides for all these niceties and, optionally, also checks that all values (both the ones explicitly specified and the ones automatically determined) are unique. Enum values are attributes of an Enumeration class instance (Volkswagen.BEETLE, Volkswagen.PASSAT, etc.). A further feature, missing in C but really quite useful, is the ability to go from the value to the corresponding name inside the enumeration (the name you get can be somewhat arbitrary for those enumerations in which you don't constrain values to be unique).

This recipe's Enumeration class has an initializer that accepts a string argument to specify the enumeration's name and a sequence argument to specify the names of all values in the enumeration. Each item of the sequence argument can be a string (to specify that the value named is one more than the last value used) or else a tuple with two items (the string that is the value's name, then the value itself, which must be an integer). The code in this recipe relies heavily on strict type checking to determine which case applies, but the recipe's essence would not change by much if the checking was performed in a more lenient way (e.g., with the isinstance built-in function).

Each Enumeration instance has two dict attributes: self.lookup to map names to values and self.reverselookup to map values back to the corresponding names. The special method _ _getattr_ _ lets you use names with attribute syntax (e.x is mapped to e.lookup['x']), and the whatis method allows reverse lookups (i.e., find a name given a value) with similar ease.

Here's an example of how you can use this Enumeration class:

if _ _name_ _ == '_ _main_ _':     import pprint     Volkswagen = Enumeration("Volkswagen",         ("JETTA", "RABBIT", "BEETLE", ("THING", 400), "PASSAT", "GOLF",          ("CABRIO", 700), "EURO_VAN", "CLASSIC_BEETLE", "CLASSIC_VAN"          ))     Insect = Enumeration("Insect",         ("ANT", "APHID", "BEE", "BEETLE", "BUTTERFLY", "MOTH", "HOUSEFLY",          "WASP", "CICADA", "GRASSHOPPER", "COCKROACH", "DRAGONFLY"          ))     def whatkind(value, enum):         return enum._ _doc_ _ + "." + enum.whatis(value)     class ThingWithKind(object):         def _ _init_ _(self, kind):             self.kind = kind     car = ThingWithKind(Volkswagen.BEETLE)     print whatkind(car.kind, Volkswagen) # emits Volkswagen.BEETLE     bug = ThingWithKind(Insect.BEETLE)     print whatkind(bug.kind, Insect) # emits Insect.BEETLE     print car._ _dict_ _ # emits {'kind': 2}     print bug._ _dict_ _ # emits {'kind': 3}     pprint.pprint(Volkswagen._ _dict_ _)     pprint.pprint(Insect._ _dict_ _) # emits dozens of line showing off lookup and reverseLookup dictionaries

Note that the attributes of car and bug don't include any of the enum machinery because that machinery is held as class attributes, not as instance attributes. This means you can generate thousands of car and bug objects with reckless abandon, never worrying about wasting time or memory on redundant copies of the enum stuff.

See Also

Recipe 6.2 shows how to define constants in Python; documentation on the special method _ _getattr_ _ in the Language Reference and Python in a Nutshell.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net