Enumerations

Enumerations make source code easier to read because a name is shown instead of a number. It is easier to determine the meaning of ProcessType.FilesOnly than the meaning of 32. For a more explicit definition of enumerations please refer to Wikipedia.org.

Flagged Enumerations
Flagged enumerations are enumerations which have values such that any combination of enumeration values are valid. A complete explaination would be lengthy. See for a great introduction to flagged enumerations (in VBScript) in the "Bitwise Operations" section.

Methods
A few methods of enumerations are:

Variable Method
class MyEnum: unknown = 0 FOO = 1 BAR = 2 BAZ = 3

Range Method
class MyEnum: FOO, BAR, BAZ = range(3)

Class Method
Classes provide an easy and validated way of creating both flagged and unflagged enumerations. The IronPython engine translates .NET enumerations in a different manner because it does not allow adding enumeration types, but instead works more like a C# enum where a single pipe character is used to join two values.

An example of a class can be found below or at.

"""Enumeration Class

Class used to automatically handle flagged enumerations. Based on the example at http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/67107

Version	Changes 1.0		Initial version """ __author__ = 'Dag R. Calafell, III' __date__    = '2007-06-22'  # yyyy-mm-dd __module_name__ = "Enumerations Class" __short_cright__= " Creative Commons License" # http://creativecommons.org/licenses/by/3.0/ __version__ = '1.0'     # Human-Readable Version number version_info = (1,0,0)  # Easier format: if version_info > (1,2,5)

from System import Array from System.Text.RegularExpressions import Regex, RegexOptions

class EnumException(Exception): pass

class Enumeration: '''Allows for the definition of enumerations in Python. Volkswagen = Enumeration("Volkswagen",		["Jetta",		"Rabbit",		"Beetle",		("Thing", 400),		"Passat",		"Golf",		("Cabrio", 700),		"EuroVan",		"ClassicBeetle",		"ClassicVan"]) '''	def __init__(self, name, enumList, flagged = False): '''Initializes a new enumeration. name		The name of the enumeration. It is assigned to the __doc__ attribute. enumList	The enumeration list. flagged		True if the values of the enumeration are able to be added. All values of the enumeration will be exponents of 2, i.e. 2^0, 2^1, 2^2 ... returns		None '''		if type(name) != str: raise ValueError, 'The name parameter must be a string value.' if type(enumList) not in (list, tuple, Array): raise ValueError, 'The name parameter must be a string value.' self.__doc__ = name self.__flagged = bool(flagged) self.__sum = 0 # Sum of all flagged enum values for faster self.IsValid processing. self.__uniqueValues = uniqueValues = [ ] self.__uniqueId = 0 nameRegex = Regex('\\A[a-z][a-z0-9_]*\\z', RegexOptions.IgnoreCase | RegexOptions.Compiled) reservedNames = ('and','assert','break','class','continue','def','del','elif','else','except','exec','finally','for','from','global','if','import','in','is','lambda','not','or','pass','print','raise','return','try','while','yield') lookup = { } reverseLookup = { } uniqueNames = [ ] # Set the special values first for x in enumList: if type(x) == tuple: if self.__flagged: raise EnumException, 'A flagged enumeration cannot have a value which is asserted.' if len(x) != 2: raise EnumException, 'The enumeration cannot have more than one value.' x, i = x				if type(x) != str or x in uniqueNames or not nameRegex.IsMatch(x) or x in reservedNames: raise EnumException, 'The enum name (%s) must be a unique string containing only letters, numbers, & underscores and it cannot be a reserved Python word.' % x				if type(i) != int or i in uniqueValues: raise EnumException, 'The enum value (%s) must be a unique integer.' % i				uniqueNames.append(x) uniqueValues.append(i) lookup[x] = i				reverseLookup[i] = x		# Now set the auto-assigned values for x in enumList: if type(x) != tuple: if type(x) != str or x in uniqueNames or not nameRegex.IsMatch(x) or x in reservedNames: raise EnumException, 'The enum name (%s) must be a unique string containing only letters, numbers, & underscores and it cannot be a reserved Python word.' % x				uniqueNames.append(x) i = self.__genUniqueId uniqueValues.append(i) self.__sum += i				lookup[x] = i				reverseLookup[i] = x		self.lookup = lookup self.reverseLookup = reverseLookup

def __genUniqueId(self): if self.__flagged: n = self.__uniqueId if self.__uniqueId == 0: self.__uniqueId = 1 else: self.__uniqueId *= 2 else: while self.__uniqueId in self.__uniqueValues: self.__uniqueId += 1 n = self.__uniqueId self.__uniqueId += 1 return n

def __getattr__(self, attr): if attr not in self.lookup: raise AttributeError return self.lookup[attr]

def __len__(self): return len(self.reverseLookup)

def GetNames(self): return self.lookup

def IsFlagged(self): return self.__flagged

def IsValid(self, value): Determines if the supplied value is a valid value for this enumeration. if type(value) == int: if self.__flagged: # It is valid as long as the value is less than or equal to the sum of all items return value <= self.__sum else: return value in self.reverseLookup elif type(value) == str: return value in self.lookup else: return False

def NameOf(self, value): '''Determines the name of the specified value. value	Integer value of the enumeration. If the enumeration is flagged and the value is a combination of values then it will return None.''' if not self.IsValid(value): raise ValueError, 'The value is not valid.' return self.reverseLookup[value]

def PrettyPrint(self): print self.__doc__ print for v in self.lookup: print '%-15s %5s' % (v, getattr(self, v))

if __name__ == '__main__': Langs = Enumeration('Coding Languages',		['Ruby',		'Python',		('PHP', 400),		'Perl',		'PostScript',		('Java', 700),		'VisualBasic',		'IronPython']) print 'Testing' Langs.PrettyPrint # Coding Languages # 	# PHP              400 # Java             700 # Ruby               0 # Python             1 # Perl               2 # PostScript         3 # VisualBasic        4 # IronPython         5 print assert len(Langs) == 8, 'The __len__ method returned an incorrect value.' assert Langs.IsFlagged == False, 'IsFlagged should be False, but the value was: %s' % Langs.IsFlagged assert Langs.Ruby == Langs.Ruby, 'Langs.Ruby should equal Langs.Ruby.' assert Langs.PHP == Langs.PHP, 'Langs.PHP should equal Langs.PHP.' assert Langs.Java == 700, 'Langs.Java should have a value of 700.' assert Langs.IsValid(700) == True, 'The value 700 should be valid. %s was returned' % Langs.IsValid(700) assert Langs.IsValid('IronPython'), 'Langs.IsValid(\'IronPython\') should return true.' assert Langs.IsValid(Langs.Perl), 'Langs.IsValid(Langs.Perl) should be return true.' assert Langs.NameOf(700) == 'Java', 'The name of the value 700 should be Java. %s was returned.' % Langs.NameOf(700) assert Langs.NameOf(Langs.VisualBasic) == 'VisualBasic', 'The name of the value Langs.VisualBasic should be VisualBasic. %s was returned.' % Langs.NameOf(Langs.VisualBasic) try: Langs.Php raise AssertionError('Lang.Php is not defined and should raise an AttributeError.') except AttributeError: pass try: Enumeration('Test', ['C#']) raise AssertionError('Enumeration initializer should verify that all enumeration names are valid Python names.') except EnumException: pass try: Enumeration('Test', ['One Space']) raise AssertionError('Enumeration initializer should verify that all enumeration names are valid Python names.') except EnumException: pass

# Flagged enumerations myFlagged = Enumeration('School Classes This Semester', ['Math', 'Science', 'Gym', 'Cooking', 'Language'], True) print 'Testing' myFlagged.PrettyPrint # School Classes This Semester # 	# Math               0 # Science            1 # Gym                2 # Cooking            4 # Language           8 print assert len(myFlagged) == 5, 'The __len__ method returned an incorrect value. %s was returned.' % len(myFlagged) assert myFlagged.IsFlagged == True assert myFlagged.Gym == myFlagged.Gym assert myFlagged.Gym + myFlagged.Cooking != myFlagged.Gym assert myFlagged.IsValid(3)

Considerations

 * Performance
 * Variables are quickly accessed and allow compiler optimizations (if any).
 * Classes which override __getitem__ or __getattr__ are slower.


 * Bit-flags
 * Variables only support bit-flags when you assign the correct values (0, 1, 2, 4, 8, 16, 32, ...).
 * Classes can auto-assign values to members.


 * Ease of Use
 * Variable declaration can be time-consuming to maintain.
 * Why are you using Python? The obvious answer is that it is faster to code and prototype because it is not a strongly-typed language where each variable must be declared.  Therefore why not have a class that makes all enumeration declarations faster and easier?
 * Classes can check that all values are unique and of the same data type.

Conclusions
There are many options when creating enumerations in [Iron]Python. What you choose should depend on what application is needed. If execution speed is a huge consideration, then the maintainance of a variable list seems trivial for the performance gained. If coding speed is most important then a class can simplify creating large enumerations.

Back to Contents.