Lexical Dispatch in Python

2008-02-09Comments

A recent article on Ikke’s blog shows how to emulate a C switch statement using Python. (I’ve adapted the code slightly for the purposes of this note).

def handle_one():
    return 'one'
    
def handle_two():
    return 'two'
    
def handle_three():
    return 'three'
    
def handle_default():
    return 'unknown'
    
cases = dict(one=handle_one,
             two=handle_two,
             three=handle_three)
    
for i in 'one', 'two', 'three', 'four':
    handler = cases.get(i, handle_default)
    print handler()

Here the cases dict maps strings to functions and the subsequent switch is a simple matter of looking up and dispatching to the correct function. This is good idiomatic Python code. When run, it outputs:

one
two
three
unknown

Here’s an alternative technique which I’ve sometimes found useful. Rather than build an explicit cases dict, we can just use one of the dicts lurking behind the scenes — in this case the one supplied by the built-in globals() function. Leaving the handle_*() functions as before, we could write:

for i in 'one', 'two', 'three', 'four':
    handler = globals().get('handle_%s' % i, handle_default)
    print handler()

Globals() returns us a dict mapping names in the current scope to their values. Since our handler functions are uniformly named, some string formatting combined with a simple dictionary look-up gets the required function.

A warning: it’s unusual to access objects in the global scope in this way, and in this particular case, the original explicit dictionary dispatch would be better. In other situations though, when the scope narrows to a class or a module, it may well be worth remembering that classes and modules behave rather like dicts which map names to values. The built-in getattr() function can then be used as a function dispatcher. Here’s a class-based example:

Sketch of an XHTML paragraph formatting class
PLAIN, BOLD, LINK, DATE = 'PLAIN BOLD LINK DATE'.split()

class Paragraph(object):
    def __init__(self):
        self.text_so_far = ''
    
    def __str__(self):
        return self._do_tag(self.text_so_far, 'p')
    
    def _do_tag(self, text, tag):
        return '<%s>%s</%s>' % (tag, text, tag)
    
    def do_bold(self, text):
        return self._do_tag(text, 'b')
    
    def do_link(self, text):
        return '<a href="%s">%s</a>' % (text, text)
    
    def do_plain(self, text):
        return text
    
    def append(self, text, markup=PLAIN):
        handler = getattr(self, 'do_%s' % markup.lower())
        self.text_so_far += handler(text)
        return self

Maybe not the most fully-formed of classes, but I hope you get the idea! Incidentally, append() returns a reference to self so clients can chain calls together.

>>> print Paragraph().append("Word Aligned", BOLD
...                 ).append(" is at "
...                 ).append("http://wordaligned.org", LINK)
<p><b>Word Aligned</b> is at <a href="http://wordaligned.org">http://wordaligned.org</a></p>

By the way, “Lexical Dispatch” isn’t an official term or one I’ve heard before. It’s just a fancy way of saying “call things based on their name” — and the term “call by name” has already been taken