Python’s introspection facilities let you add a version of import types, string, pprint, exceptions class EnumException(exceptions.Exception): pass class Enumeration: def _ _init_ _(self, name, enumList, valuesAreUnique=1): self._ _doc_ _ = name lookup = { } reverseLookup = { } i = 0 uniqueNames = {} uniqueValues = {} for x in enumList: if type(x) == types.TupleType: x, i = x if type(x) != types.StringType: raise EnumException, "enum name is not a string: " + x if type(i) != types.IntType: raise EnumException, "enum value is not an integer: " + i if uniqueNames.has_key(x): raise EnumException, "enum name is not unique: " + x if valuesAreUnique and uniqueValues.has_key(i): raise EnumException, "enum value is not unique for " + x uniqueNames[x] = 1 uniqueValues[i] = 1 lookup[x] = i reverseLookup[i] = x i = i + 1 self.lookup = lookup self.reverseLookup = reverseLookup def _ _getattr_ _(self, attr): try: return self.lookup[attr] except KeyError: raise AttributeError def whatis(self, value): return self.reverseLookup[value] In C, Python has an accepted idiom that’s fine for small numbers of constants: A, B, C, D = range(4) But this idiom doesn’t scale well to large numbers 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, while optionally verifying that all values (specified and unspecified) are unique. Enum values are attributes of an This recipe’s A, B, C, D = range(4)0 built-in function). Therefore, each instance is equipped with two dictionaries: A, B, C, D = range(4)1 to map names to values and A, B, C, D = range(4)2 to map values back to the corresponding names. The special method A, B, C, D = range(4)3 lets names be used with attribute syntax ( A, B, C, D = range(4)4 is mapped to A, B, C, D = range(4)5), and the A, B, C, D = range(4)6 method allows reverse lookups (i.e., finds a name, given a value) with comparable syntactic ease. Here’s an example of how you can use this if _ _name_ _ == '_ _main_ _': 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 demo(lines): previousLineEmpty = 0 for x in string.split(lines, "\n"): if x: if x[0] != '#': print ">>>", x; exec x; print previousLineEmpty = 1 else: print x previousLineEmpty = 0 elif not previousLineEmpty: print x previousLineEmpty = 1 def whatkind(value, enum): return enum._ _doc_ _ + "." + enum.whatis(value) class ThingWithType: def _ _init_ _(self, type): self.type = type demo(""" car = ThingWithType(Volkswagen.BEETLE) print whatkind(car.type, Volkswagen) bug = ThingWithType(Insect.BEETLE) print whatkind(bug.type, Insect) print car._ _dict_ _ print bug._ _dict_ _ pprint.pprint(Volkswagen._ _dict_ _) pprint.pprint(Insect._ _dict_ _) """) Note that attributes of A, B, C, D = range(4)8 and A, B, C, D = range(4)9 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 A, B, C, D = range(4)8 and A, B, C, D = range(4)9 objects with reckless abandon, never worrying about wasting time or memory on redundant copies of the enum stuff. Recipe 5.16, which shows how to define constants in Python; documentation on A, B, C, D = range(4)3 in the Language Reference. |