One good use for default function arguments

December 12, 2012

When I wrote about a danger of default function arguments I mentioned that there are cases where they make sense and are useful. Today I'm going to present what I feel is one of them.

To put it simply, one use for default arguments is when you effectively have a bunch of slightly different APIs but it would be awkward to have different functions for them. Not uncommonly you might have too many function variants, the code would be too entwined, or both. Reasonably chosen default arguments effectively give you multiple APIs; one with all the defaults, another with one set of defaults overridden, and so on. You can in fact discover how many different APIs you actually need more or less on the fly, as you write code that uses different combinations of default and non-default arguments.

All of that sounds really abstract, so I'll use an actual example from DWiki. DWiki has a concept of 'views', which are both different ways to present the same underlying bit of the filesystem and different ways of processing URLs. Views have names and handler functions, and there is a registration function for them:

def register(name, factory, canGET = True, canPOST = False,
             onDir = False, onFile = True, pubDir = False,
             getParams = [], postParams = []):
  [....]

This is effectively several APIs in one. Fully expanded, I think it'd be one API that's used to register forms (that's what canPOST, a False value for canGET, getParams, and postParams are for), one API for views of directories only, one API for views of files only, and one API for views that work on both files and directories. As separate functions, each would have a subset of the full arguments for register(). But equally, as separate functions they would all do the same thing and they'd have basically the same name (there is no natural strong name difference between 'register a form view' and 'register a directory view' and so on).

I dislike small variations of things (I'm driven to generalize), so when I was writing DWiki I didn't make separate functions for each API; instead I slammed them together into one function with a bunch of default arguments. The simplest case (with all arguments as defaults) corresponds to what I thought at the time was the most common case, or at least the base case.

(This view of default arguments creating multiple APIs comes from a bit in this talk on Go; reading it crystallized several things in my head.)


Comments on this page:

From 46.65.45.207 at 2012-12-12 05:20:35:

You're using mutable default arguments, though, which is a hazard all its own, particularly if you ever change the values of the arguments inside the function.

By cks at 2012-12-12 08:10:55:

Oops, that's embarrassing; I hadn't even noticed that until you pointed it out. Those two []'s should actually be ()'s, especially since both are actually read-only in the code. (In fact all of the callers that supply actual values use tuples.)

I wonder why I use [] way back when. Probably the issue didn't occur to me because I knew that the parameters were read-only.

Written on 12 December 2012.
« The general lesson from the need for metrics
fork() and closing file descriptors »

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

Last modified: Wed Dec 12 00:46:15 2012
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.