Welcome, guest | Sign In | My Account | Store | Cart

Although python has no literal representation for compiled regular expressions (which is good) you can compile them at Python read/compile-time. You can also use regexs to match strings and automatically call funtions with arguments that are based on the groups of the matched strings.

Python, 79 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import re

Commands = \
[
	[ 'description of pattern 1', 
		r'X (?P<num>\d),(?P<name>.*)$', 
		aClass.cmd1],
	[ 'pattern 2', 
		r'Widget (?P<part>.*)$', 
		aClass.cmd2]
]

"""
 Clobber each regex str w/compiled version
"""
for cmd in Commands:
	try:
		cmd[1] = re.compile( cmd[1] )
	except:
		print "Bad pattern for %s: %s" % ( cmd[0], cmd[1] )
		assert 0, "Doh!"
		

class myCommands:

	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 sub-exprs from the regex.
		"""
		for cmd in cmdList:
			found = cmd[1].match( str) # or .search()
			if found:
				return cmd[2]( self, *found.groups() )
		return None
	
	def runCommand( self, cmd):
		self._dispatch( Commands, cmd)
		
	def cmd1( self, num, name):
		print "The number for %s is %d" % (name, int(num))
		return 42
	def cmdWidget( self, partnum):
		print "Widget serial #: %d" % partnum
	...


"""
Method 2 - instead of a disptatch table, 
  use function attributes.
 Put these fn's in a modules called 'myCommands', for example.
"""
def cmdDoThis( name, num): 
	print "The number for %s is %d" % (name, int(num))
	return 42
cmdDoThis.patt = r'X (?P<num>\d),(?P<name>.*)$'

def cmdWidget( partnum): 
	print "Widget serial #: %d" % partnum
cmdWidget.patt = r'Widget (?P<part>.*)$'

# and later...

import sys
def autoDispatch( str, module=sys.modules[__name__] ):
	for entry in dir(module):
		# use whatever prefix you like, if desired
		if entry.startswith( 'cmd'):
			fn = cmds.__dict__[entry]
			if not fn.regobj: 
				fn.regobj = re.compile( fn.cmd )
			found = fn.regobj.match( str)
			if found:
				return fn( *found.groups() )

import myCommands
...
autoDispatch( 'Widget 1234A', myCommands)

In the above example, r = self.runCommand("X 36,Mike") would automatically call cmd1(self, "36", "Mike") and bind the variable 'r' to 42.

Of course, this example may be better handled by testing str[0] and using str.split(','), but more complex forms are a good candidate for regular expressions.

An idiomatic approach is also to put pattern to be compiled directly in the structure to be created at load-time. For example, Cmds = ( (re.compile("^patt1$", fn), ... ) This is simple if you won't require any special processing and don't want to have the option of keeping the original ascii regex around - plus it looks a little prettier to me to not include code in data-struct initializers.

Method 1 uses an explict dispatch table, while method two uses function attributes to store ancillary data - they are equally powerful, so it depends on which you like better.

Several variations/additions are possible: (1) Derive the fn to be called from the pattern description, rather than listing it in the table. (A little too dynamic for my taste.) (2) Modify to use outside of a class. (Remove self's and bound-methods) (3) Using the regex pattern string at compile-time, derive and remember types so they can be coerced before they are passed to the proper function. (For example, pass an int if r'\d' was matched.) (4) Clobber the string regex's on-demand, rather than up-front (But this way you at least know they're well-formed right away.) (5) Include optional re.compile flags in the dispatch table (6) Include test-cases directly in the table and run them as they are compiled. (Like a mini "doctest".) (7) Have _dispatch pass state information in a mutable object between invocations. (8) Have _dispatch pass keyword arguments based on group name, rather than positional args. (Or pass a dict as a single arg.) (9) Remove descriptions if you don't need them (10) Write Dispatcher as a class itself and sublass to provide callable fn's and patterns. (11) Depend on the regex cache, rather than storing pre-compiled regexes explicitly.

See also: - The "cmd" module can also be used to write simple command interpreters.

1 comment

Martin Miller 21 years, 2 months ago  # | flag

Poorly Presented Examples. There appear to be numerous typos an other related errors in the example code provide, but I think I understand the idea and believe it's a pretty good one, namely dispatching based on regular expression pattern matching.

Created by Michael Robin on Thu, 18 Oct 2001 (PSF)
Python recipes (4591)
Michael Robin's recipes (3)

Required Modules

  • (none specified)

Other Information and Tasks