Using Python introspection for semi-evil

November 9, 2005

One of the things I write in Python is network daemons. Because it works so nicely, network daemons usually take input as text lines that start with a command word and have whitespace separated arguments. There's a certain amount of eye-rolling tedium in writing code to check that the command word is valid and has the right number of arguments. When I wrote a program that does a lot of this, the tedium finally got to me and I sort of snapped and automated all of the validation through Python's introspection features.

The program's basic structure has an object for each connection. Each command is handled by a method function on the object, and the functions all take normal argument lists. (So they are not passed the command line as a big list or anything.)

The name of each command method is 'op_<command>', eg 'op_sample' to handle the 'sample' command. To check whether the line's first word was a valid command, I just looked to see if the connection's object had an attribute with the appropriate name.

(This is a fairly common pattern in Python; see, for example, how BaseHTTPServer handles dispatching GET and POST and so on.)

To check that the number of arguments was right, I reached into the handler function I'd just found and fished out how many arguments it expected to be called with (compensating for the additional 'self' argument that method functions get). This isn't at all general, but I didn't need generality; the network daemon's commands all have a fixed number of arguments.

The code wound up looking like this:

# in a class:
def op_sample(self, one, two):
  ...

def dispatch(self, line):
  # Do we have a line at all?
  n = line.split()
  if not n:
    return False
  cword = 'op_' + n[0]

  # Valid command word?
  cfunc = getattr(self, cword, None)
  if not cfunc:
    return False

  # Right argument count?
  acnt = cfunc.func_code.co_argcount
  if acnt != len(n)
    return False

  return cfunc(*n[1:])

(The real version used more elaborate error handling for 'empty line', 'no such command', and 'wrong number of arguments'.)

Normally I would have to account for the extra self argument in the function's argument count. But in this code the n list has one extra element (the command word) too, so it balances out. This has the subtle consequence that you can't make op_sample a staticmethod function, because then it would have the wrong argument count.

(I did say this was semi-evil.)

Written on 09 November 2005.
« Fun with DNS: a semi-obscure problem
How doing relative name DNS lookups can shoot you in the foot »

Page tools: View Source, Add Comment.
Search:
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Wed Nov 9 02:30:10 2005
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.