views:

135

answers:

5

I'm writing a parser/handler for a network protocol; the protocol is predefined and I am writing an adapter, in python.

In the process of decoding the incoming messages, I've been considering using the idiom I've seen suggested elsewhere for "switch" in python: use a hash table whose keys are the field you want to match on (a string in this case) and whose values are callable expressions:

 self.switchTab = { 'N': self.handleN,
                    'M': self.handleM,
                     ...
                  }

Where self.handleN, etc., are methods on the current class.

The actual switch looks like this:

 self.switchTab[selector]()

According to some profiling I've done with cProfile (and Python 2.5.2) this is actually a little bit faster than a chain of if..elif... statements.

My question is, do folks think this is a reasonable choice? I can't imagine that re-framing this in terms of objects and polymorphism would be as fast, and I think the code looks reasonably clear to a reader.

A: 

It is a very valid and reasonable design choice. IIRC it's called a function lookup/dispatch table.

The benefits are:

  • Speed, as you noted.

  • Maintenance. Adding another choice doesn't change the code for any previous choices, and the handlers are highly modularized.

  • Readability. A single switch hash is IMHO easier to read than a bunch of if-elses.

  • You can sometimes automate the lookup hash population, for certain scenarios (sometimes using reflection if your language supports it).

The cons are:

  • You can only use this if your universe of valid keys is an enum, or if there's an obvious function converting them to enum.

  • You need to remember to add the functions to the lookup table. Not really a cons since any other methodology requires you to maintain the choosing logic, and as noted above, sometimes it's a pro since it can be automated.

DVK
What do you mean "an enum"?
Mike Graham
An enumerated type. A finite set of values of the same type.
DVK
A: 

That's exactly how I would (and have) done it.

Daniel Stutzbach
A: 

I think your design is sane. I worked on a networking project in Java where I had a table just like this, and it worked well to a point. After you start getting several operations however, you may want to consider using something like a reflection look-up instead of maintaining an explicit table, as maintaining a large table like this can get tedious.

James Kingsbery
Yeah, I hear you. This is a very limited scope thing, interfacing to a simple legacy protocol, it won't grow beyond 5 or so entries.
the ungoverned
Doing this by reflection is non-explicit and possibly insecure. It is best to build a separate dict for this express purpose, either manually or by registering the appropriate methods.
Mike Graham
Well, the only answer that got up-voted was one that used reflection.
James Kingsbery
A: 

If switchTab is constant you could declare it in the class instead of the instance.

class MyClass(object):
    def handleM(self):
        print "Handling M...", self
    def handleN(self):
        print "Handling N...", self
    def dispatch(self, c):
        self.switchTab[c](self)
    switchTab={'M': handleM, 'N': handleN}

mc = MyClass()
mc.dispatch("M")
mc.dispatch("N")

You can also use a decorator to build switchTab from the methods starting with 'handle' But you'll have to do something extra to handle "@" for example

def withHandlers(c):
    c.switchTab = dict((k[6:],v) for k,v in vars(c).items() if k[:6]=='handle')
    return c

@withHandlers
class MyClass(object):
    def handleM(self):
        print "Handling M...", self
    def handleN(self):
        print "Handling N...", self
    def dispatch(self, c):
        self.switchTab[c](self)

mc = MyClass()
mc.dispatch("M")
mc.dispatch("N")
gnibbler
Since its values are bound methods, you would have to change the approach slightly if you did this.
Mike Graham
True, you'd have to dispatch with `self.switchTab[selector](self)`
gnibbler
+4  A: 

No need for a dispatch table, you can call the handler programatically.

def dispatch(self, c):
    return getattr(self, 'handle' + c)()
Paul Hankin