Thinking about the best way to handle command registration

August 17, 2008

Suppose that you have a Python utility program that does a number of related things; it's invoked as 'program command ....', and decides what to do based on what the command is. The obvious way to implement this in Python is to have a function for each command and a top-level dispatcher that looks up the function to call for each command and passes it the arguments to process. Clearly you don't want to hard-code this in the dispatcher function in a big if/elif block; instead you want to somehow register all the commands in a simpler, easier to maintain way.

Since I have been writing such a utility program, I have found myself wondering what the best way of doing this is (where 'best' means 'most natural, obvious, and easy to follow'). So far I've come up with a number of possible ways:

  • have a dictionary where the key is the command name and the value is a tuple of information about the command (including the function to call, the minimum and maximum number of command line arguments it takes, and some help text).

    (The obvious and simple approach, but it starts getting more and more awkward as you add more per-command data to keep track of. You can use a structure instead of a tuple, but then you are complicating the simple scheme.)

  • call a registration function to define a command, giving it all of the details:
    def cmdfunc(args, ...):
      <whatever>
    register('cmd', cmdfunc, ...)

    (The registration function can have defaults for various things.)

  • use a decorator function to do the registration:
    @register('cmd', ...)
    def cmdfunc(args, ...):
      <whatever>

  • attach all of the information to the function as function attributes, and have a simple scheme to relate the command to the function name. The top level dispatcher then uses introspection to find if the command exists and to pull out anything else it needs.

Naturally you can combine aspects of all of these together to create hybrid systems (for example, using the function's docstring for some of the user help text).

Of these, I suspect that the decorator function approach is the most Pythonic but is also the second hardest for people who are not experienced Python programmers to follow (introspection would probably be the hardest). Since that last issue is a consideration, I am leaning towards using the plain registration function approach.

(My current code mostly uses the simple dictionary approach, but it's not the clearest thing to follow.)


Comments on this page:

By Dan.Astoorian at 2008-08-18 10:38:31:

Another method is to have the dispatcher for 'program command ....' try to import a module named for command from a specific directory, and invoke a function in that module.

This has the advantage of making registrations implicit (adding a new subcommand is a matter of dropping the module into a directory). It also means that you only need to load the code for the command the user actually wants to run, which means that a) it may run faster, particularly if some of the commands take a lot of code to implement, and b) new subcommands can be plugged in with confidence that they will not interfere with existing ones.

One disadvantage, of course, is that listing the available subcommands (e.g., 'program help') becomes more complex--one must determine the set of importable modules which contain the function name that the mainline will invoke.

This method is probably overkill for casual or small-scale projects, but I've found it useful.

--Dan

By nothings at 2008-08-18 16:51:12:

Clearly you don't want to hard-code this in the dispatcher function in a big if/elif block

This is not clear to me at all. It seems like an awful lot of infrastructure that makes the code flow less clear for no obvious benefit.

By cks at 2008-08-19 11:23:13:

In thinking about it, I think that the problem with a big if/elif block is the same problem with putting structures in tuples: the structure of the situation only exists implicitly in the code, instead of being explicit (where people reading the code can see it clearly).

(In my case it's also not only in the dispatcher function, as there's a 'help' option to print what commands are available and so on.)

The other personal reason is that I hate boring boilerplate code; it gives me hives to write it.

From 213.155.151.233 at 2008-08-26 07:41:58:

You could also have a class per subcommand, with said class implementing methods to handle flags and do the actual work. Then you can use any of the above approaches to register the class - I personally prefer a dict of them.

Written on 17 August 2008.
« Why your blog comments have less of an audience than new blog entries
The problem with using tuples and lists to hold structures »

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

Last modified: Sun Aug 17 23:38:03 2008
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.