Dispatching Based on Pattern Matches

Credit: Michael Robin

Problem

You need to use regular expressions to match strings and then automatically call functions with arguments based on the matched strings.

Solution

Once again, a class offers a good way to package together some state and some behavior:

import re

class Dispatcher:

    def _dispatch(self, cmdList, str):
        """ Find a match for str in the cmdList and call the associated
           method with arguments that are the matching grouped subexpressions
           from the regex.
        """
        for comment, pattern, command in cmdList:
             found = pattern.match(str)   # or, use .search(  )
             if found: return command(self, *found.groups(  ))

    def runCommand(self, cmd):
        self._dispatch(Commands, cmd)

    # example methods

    def cmd1(self, num, name):
        print "The number for %s is %d" % (name, int(num))
        return 42

    def cmd2(self, partnum):
        print "Widget serial #: %d" % int(partnum)

Commands = [
      [ 'Number-to-name correspondence',
              r'X (?P<num>\d+),(?P<name>.*)$',
              Dispatcher.cmd1],
      [ 'Extract Widget part-number',
              r'Widget (?P<partnum>.*)$',
              Dispatcher.cmd2],
]

# Prepare the Commands list for execution by compiling each re for cmd in Commands:
    try:
     cmd[1] = re.compile( cmd[1] )
    except:
     print "Bad pattern for %s: %s" % ( cmd[0], cmd[1] )

Discussion

In Python, it’s generally best to compile regular expressions into re objects. The re module does some caching of string-form regular expressions that you use directly, but it’s still better to make sure that regular expressions are not needlessly recompiled. The string form is still available as r.pattern for any compiled re object r, anyway, should you need it (e.g., for debugging/logging purposes).

You can use regular expressions to match strings (or search into strings) and automatically call appropriate functions, passing as arguments substrings of the matched string that correspond to the groups of the regular expression.

This recipe exemplifies one approach to this solution. The idea is that:

r = self.runCommand("X 36,Mike")

automatically calls:

cmd1(self, "36", "Mike")

and binds the variable r to 42, the result of cmd1.

This specific example might be best approached with direct string manipulation (testing str[0], then using the split method of strings), but regular expressions let you handle much more complicated cases with nearly equal ease.

An idiomatic Pythonic approach is to put each pattern to be compiled directly in the structure to be created at load-time. For example:

Cmds = ( (re.compile(r"^pa(t)t1$"), fn), ... )

This is simple, if you don’t require any special processing, but I think it’s a little prettier to avoid including code in data-structure initializers.

See Also

Documentation for the re module and regular-expression objects in the Library Reference.

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.