Enumerations
From IronPython Cookbook
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.
Contents |
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 [1] 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 [2].
"""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 not self.lookup.has_key(attr):
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 self.reverseLookup.has_key(value)
elif type(value) == str:
return self.lookup.has_key(value)
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.

