3.20 Dispatching Based on Pattern Matches
Credit: Michael Robin
3.20.1 Problem
You need to use regular
expressions to match strings and then automatically call functions
with arguments based on the matched strings.
3.20.2 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] )
3.20.3 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.
3.20.4 See Also
Documentation for the re module and
regular-expression objects in the Library
Reference.
|