‘The enum datatype (in python)’

6. Aug 2013, Lukas Prokop

with lukas.brain:
    python = 42

PyGraz Logo

http://lukas-prokop.at/talks/pygraz-enum

The enum datatype

Families:

Enum in other languages (structured)

C, C++, C#
enum name { value1, value2, value3 }
Pascal
var (implicitly, parenthesised list)
Modula-3
TYPE name = {value1, value2, value3 };
Ada [not necessarily structured]
parenthesised list
Algol
MODE name = dtype; MODE value1 = value1, …

C, C++, Java

C/C++:

enum fruits { apple, banana, cherry };
enum fruits { apple = 0, banana = 1, cherry = 2 };

Java:

enum Fruits {
  APPLE(0), BANANA(1), CHERRY(2);
  private int value;
  Fruits(int value) { this.value = value; }
  public int value() { return value; }
}
enum Fruits {
   APPLE, BANANA, CHERRY
};

Enum in other languages (others, dynamic, functional)

Go
iota creates sequential integer constants (but does not create real enum datatype)
ruby
use symbols in a Hash or create a module with values as members
Clojure
use symbols
Haskell
data derived from Enum

Go, Scala

const (
   apple = iota
   banana
   cherry
)
object Fruit extends Enumeration {
  val Apple, Banana, Cherry = Value
}

sealed abstract class Fruit
case object Apple extends Fruit
case object Banana extends Fruit
case object Cherry extends Fruit
      

Clojure, Haskell

(def fruits #{:apple :banana :cherry})
 
(defn fruit? [x] (contains? fruits x))
(def fruit-value (zipmap fruits (iterate inc 1)))

(println (fruit? :apple))
(println (fruit-value :banana))
data Fruit = Apple | Banana | Cherry deriving Enum

So we have it in programming languages!

And how/when to use them?

Usecase #1: Whitelisting

Set of possible values [only useful if no other values can be assigned to value of this enum type].

    enum DateConfig { ISO8601, RFC5322 }
    enum Permissions { READ, WRITE, EXEC }
    enum Grades { A, B, C, D, F }

Be aware that it's probably better to keep the values binary disjunct (Name.Value1 & Name.Value2 = 0) [depending on whether or not you want to apply binary operators]. So the Permissions values are actually { READ = 0, WRITE = 1, EXEC = 2 } whereas they are { READ = 4, WRITE = 2, EXEC = 1 } on UNIX.

Compare this with the {True,False}-discussion in python.

Type–token distinction.

Usecase #2: State Machine

One state triggers certain component. Result of component defines which state to enter next (or unconditionally).

state = INIT
requests = 42
while True:
    if state == INIT:
        initialize()
        state = READY
    elif state == READY
        if requests == 0:
            state = SHUTDOWN
            continue

        success = handle_request()
        if success:
            state = READY
        else:
            state = ERROR
        requests -= 1
    elif state == ERROR:
        logging.error("Error occured while handling request")
        state = SHUTDOWN
    elif state == SHUTDOWN:
        teardown()

So, what about python?

Good mailinglist post summing up design ideas (by Michael Foord)

Enum features

Guido's open issue list in April 2013

(Adapted to our rosettacode example and summarized).

  1. iter(Fruit) returns elements in definition order? Yes, let's agree on that.
  2. Fruit(val) allowed? For example:
    class Fruit(Enum):
       apple = 1
       banana = 2
       cherry = 3
    
    x = Fruit.apple
    assert Fruit(x) is Fruit.apple
    assert Fruit(1) is Fruit.apple
    Fruit(42)  # raises
  3. Fruit('apple') is Fruit.apple. No, rejected. getattr(Fruit, 'apple') is possible anyway and bool('False') doesn't return False anyway.
  4. Let's not hypergeneralize and try to make bool and enums completely alike. [...] Type bool is its own thing, but that doesn't mean enums can't emulate aspects of it.

Hacking the python

Using objects instead of numbers?

>>>> a = object()
>>>> b = object()
>>>> a == b
False

… achieving uniqueness, no ordering.

Integers as enum value would mean comparisons between enums of unrelated types is misleading.

API design

Using a class with class variables? So what is instantiation for? Inheritance is obviously extension. Mixins are uncommon for extension of homogeneous members.

Design 1 [PEP 354]

>>> Weekdays = enum('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat')

Design 2 [PEP 435]

>>> class Color(Enum):
...     red = 1
...     green = 2
...     blue = 3

Hacking the python

APPLE, BANANA, CHERRY = range(3)

val = BANANA
val == BANANA

 Intuitive typing behavior
 String representation
 Add values at runtime
 Hiding value index
 Assigning indices automatically
 Ordering

Hacking the python

Group = make_constants('Group', name1=value1, name2=value2)
name1, name2 = Group.name1, Group.name1
flag = name1 | name2

via Michael Foord

? Intuitive typing behavior
? String representation
 Add values at runtime
 Hiding value index
 Assigning indices automatically
 Ordering

PEP 435 Implementation

>>> from enum import Enum
>>> class Fruit(Enum):
...     apple = 1
...     banana = 2
...     cherry = 3

Fruit is the enum.
apple is an enum member.
1 is the enum value.

PEP 435 Implementation

Friendly string representation.

>>> print(Fruit.apple)
Fruit.apple
>>> print(repr(Fruit.apple))
<Fruit.apple: 1>

Typing system.

>>> type(Fruit.apple)
<Enum 'Fruit'>
>>> isinstance(Fruit.apple, Fruit)
True
>>> print(Fruit.apple.name)
apple

PEP 435 Implementation

Iteration

>>> class Fruit(Enum):
...   apple = 42
...   banana = 3
...   cherry = 11
...
>>> for fruit in Fruit:
...   print(fruit)
...
Fruit.apple
Fruit.banana
Fruit.cherry

Hashable

>>> plantation = {}
>>> plantation[Fruit.apple] = 'Available in sector C'
>>> plantation[Fruit.banana] = 'Not available in summer 2013'
>>> plantation
{<Fruit.apple: 1>: 'Available in sector C',
 <Fruit.banana: 2>: 'Not available in summer 2013'}

Duplicate values

>>> class Fruit(Enum):
...     apple = 1
...     apple = 2
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in Fruit
  File "/usr/local/lib/python3.4/enum.py", line 88, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'apple'
>>> class Fruit(Enum):
...     apple = 1
...     banana = 1
...     cherry = 2
... 
>>> list(Fruit)
[<Fruit.apple: 1>, <Fruit.cherry: 2>]
>>>

Accessing members

>>> for name, member in Fruit.__members__.items():
...   name, member
...
('square', <Fruit.square: 2>)
('diamond', <Fruit.diamond: 1>)
('circle', <Fruit.circle: 3>)
('alias_for_square', <Fruit.square: 2>)

Comparisons

>>> Fruit.apple is Fruit.apple
True
>>> Fruit.apple is Fruit.cherry
False
>>> Fruit.apple is not Fruit.cherry
True
>>> Fruit.apple < Fruit.cherry
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: Fruit() < Fruit()
>>> Fruit.cherry == Fruit.apple
False
>>> Fruit.cherry != Fruit.apple
True
>>> Fruit.cherry == Fruit.cherry
True
>>> Fruit.cherry == 2
False

Functional API

>>> Animal = Enum('Animal', 'ant bee cat dog')
>>> Animal
<Enum 'Animal'>
>>> Animal.ant
<Animal.ant: 1>
>>> Animal.ant.value
1
>>> list(Animal)
[<Animal.ant: 1>, <Animal.bee: 2>, <Animal.cat: 3>, <Animal.dog: 4>]

Finally; thanks!

PEP 435 contains many API examples.
Python 3.4 ships the enum package.

 Intuitive typing behavior
 String representation
 Add values at runtime
 Hiding value index
 Assigning indices automatically
 Ordering

Have fun using it!