I once tried to give Python something like C's enums, as described here: http://groups.google.com/groups?selm=G6qzLy.6Fo%40world.std.com That approach tried to assign to a dictionary returned by the locals() function, intending that such assignments would become class attributes. The Tim-bot explained to me the errors of my ways. The quest for the perfect Python enum goes on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | import types, string, pprint, exceptions
class EnumException(exceptions.Exception):
pass
class Enumeration:
def __init__(self, name, enumList):
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 x in uniqueNames:
raise EnumException, "enum name is not unique: " + x
if i in uniqueValues:
raise EnumException, "enum value is not unique for " + x
uniqueNames.append(x)
uniqueValues.append(i)
lookup[x] = i
reverseLookup[i] = x
i = i + 1
self.lookup = lookup
self.reverseLookup = reverseLookup
def __getattr__(self, attr):
if not self.lookup.has_key(attr):
raise AttributeError
return self.lookup[attr]
def whatis(self, value):
return self.reverseLookup[value]
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)
# Notice that car's and bug's attributes don't include any of the
# enum machinery, because that machinery is all CLASS attributes and
# not INSTANCE attributes. So you can generate thousands of cars and
# bugs with reckless abandon, never worrying that time or memory will
# be wasted on redundant copies of the enum stuff.
print car.__dict__
print bug.__dict__
pprint.pprint(Volkswagen.__dict__)
pprint.pprint(Insect.__dict__)
""")
|
In C, enums allow you to declare a bunch of constants with unique values, without necessarily specifying the actual values (except in cases where you need to). Python has an accepted idiom that's fine for very small numbers of constants (A, B, C, D = range(4)) but it doesn't scale well to large numbers, and it doesn't allow you to specify values for some constants while leaving others unspecified. This approach does those things, while verifying that all values (specified and unspecified) are unique. Enum values then are attributes of an Enumeration class (Volkswagen.BEETLE, Volkswagen.PASSAT, etc.).
A different approach. I like it more complicated :-) The following class allows you to use enums as like colors.red, to convert like colors["red"], get the string value like someColor.asString and some other nifty things. Of course, the overhead is higher.
Here it is ...
(comment continued...)
(...continued from previous comment)
Re: A different approach. On 2001/08/29 Michael Radziej wrote:
> I like it more complicated :-)
That's fine, as long as the code isn't broken and is complete, which does not seem to be the case in what was posted. Specifically there was a crucial line missing from Enum.__init__(), the cleverness in the if in Enum.__contains__() messed up when the default start values was used, and a number other methods need for the examples were missing.
Below is a complete and functional version that was tested with Python 2.4. There are comments for most of the changes made.
Additional observations: The list contained in the _byInt attribute could be quite long if the starting integer value is is a big number. Also, some of the methods are fairly inefficient in those circumstances.
(comment continued...)
(...continued from previous comment)
(comment continued...)
(...continued from previous comment)
Specified values should be considered before unspecified. This fixes a minor bug that would have become apparent if my test case had used specified values that might have collided with the normal numbering (e.g. 4 for Volkswagen.THING instead of 400).