Earlier in the week, I made my first screencast to give an overview of PEP 435 which introduces enums to the python standard library.
The code below is the set of python notes I had prepared as I made the screencast.
# What are enumerations ? # A series of immutable discrete arbitrary values # of some type # may or may not have a semantic meaning # Why need them as a separate construct ? # You can represent them as constants with values eg. ints # But what does GREEN * 2 mean ? # And if GREEN = 2, when you print / log GREEN, you expect to see GREEN not 2 # And sometimes you want to treat all the values as a set eg. days of week # PEP 435 is the formal python document introducing enums # Implemented in 3.4. You will need a python 3.4 runtime # How are enumerations constructed ? from enum import Enum # There are two ways to construct enums. The first is a functional API. eg. Direction = Enum('Direction','North East South West') # Direction is an enumeration # It has 4 members list(Direction) # Each member has a name and an associated value # This is more apparent when you render the __repr__ of the member list(Direction)[0] # in this case the values were automatically provided. # When values are automatically generated they are integers starting from 1 # You could also pass the member name and value as 2 tuples StrDirection = Enum ('StrDirection',(("North", "N"),("East","E"), ("West", "W"), ("South", "S"))) list(StrDirection) # Or as a dict AngleDirection = Enum ('AngleDirection',{"North": 90,"East":0, "West": 270, "South": 180}) list(AngleDirection) # Note: while Direction is an iterable in definition order, # you can't pick up the zero'th element eg. Direction[0] # ie. you cannot index by position # However Direction.North and Direction["North"] are both valid Direction.North Direction["North"] # Curiously a enum can also be looked up using a value by using the call operator Direction(1) # A key property of an enum is that its members are hashable # Internal structure. # Every Enum type has a __members__ attribute of the type OrderedDict Direction.__members__ list(Direction.__members__.keys()) list(Direction.__members__.values()) list(Direction.__members__.items()) # enums are compared by identity and can be checked for equality # however you cannot perform ordered comparisons # Aliases are not returned when iterating # While two members cannot share the same name, they can share the same value (aliases) # There is only one member that is shared in case of aliases print(list(name for name, member in Colour.__members__.items() if member.name != name)) # An alternative way of defining an enum is a class # Enum classes can have instance and classmethods class Timezone: def __init__(self,offset,dst) : self.offset = offset self.dst = dst def earlier_than(self, other) : # ignoring dst return self.offset < other.offset def __eq__(self,other) : return self.offset == other.offset and self.dst == other.dst def __str__(self) : return "TZ({},{})".format(self.offset, self.dst) def __repr__(self) : return "TZ({},{})".format(self.offset, self.dst) class Timezones(Enum): IST = Timezone(330, False) GMT = Timezone(0, False) UTC = Timezone(0, False) EST = Timezone(-300, False) @classmethod def all_zones(cls): # Aliases are not returned when iterating return list(n.name for n in cls) @classmethod def aliases(cls): return [(name, member.name) for name, member in cls.__members__.items() if member.name != name] def offset(self) : return self.value.offset for tz in Timezones : print(tz) Timezones.all_zones() Timezones.IST.offset() list(Timezones) list(Timezones.__members__.keys()) list(Timezones.__members__.values()) id(Timezones.GMT) id(Timezones.UTC) Timezones.IST == Timezones.GMT Timezones.GMT == Timezones.IST Timezone.UTC < Timezone.IST # An enum which does not have any members can be subclassed. One which has cannot be # So far the values per se have not been important (except to distinguish between distinct / aliased members) # However in some cases it is important, and for that a special case Enum ie. IntEnum is defined from enum import IntEnum class Education(IntEnum): Kindergarten = 1 Primary = 2 Secondary = 3 HigherSecondary = 4 Graduate = 5 # elsewhere eg. US, this would be under graduate PostGraduate = 6 # and this would be called graduate Doctorate = 7 # This is an example where the value (and its ordering particularly is considered important) # Now operators relevant to the semantics of the mixed in type (in this case int) can be used Education.Secondary > Education.Primary # Note that this opens up semantic issues of equivalence across enum types. eg. class Colour(IntEnum): Red = 1 Green = 2 Blue = 3 Education.Secondary == Colour.Blue # Thus you can choose to bring in any value types consistent with their appropriate semantics # Note that this could lead to unexpected results eg. list(range(Education.Graduate)) # However IntEnum is just a special case of being able to mix in values of any types ie. class IntEnum(int, Enum) : pass # So use value types that are semantically consistent