Posted by Kevin D Smith @ 2:59 am on January 27th 2010

Seeing Stars in Python’s Function Definitions

I had a problem today that required the used of a couple of mandatory arguments and a bunch of keyword arguments that got converted to a piece of XML. The easy solution would have been simply to specify each mandatory argument and just use **kwargs to pick up the keyword arguments as shown below.

def foo(x, y, **kwargs):
   ...

Then in the body of the function, I could sort through the values in the kwargs dictionary to get the values. The problem is that while this would work, it puts all work of checking the keys of the kwargs dictionary on my shoulders rather than Python’s shoulders where it belongs. It also means that I would have to check for any missing keys and put default values into those slots (another thing that Python’s keyword specifiers allow me to do in the function definition). This is just unacceptable. I need Python to do it’s job, while still maintaining the ease of use of **kwargs. The other problem is that I now have kwargs with my keyword values, and x and y in local variables. It was this last thought that gave me an idea for a simple solution.

If you put all of your arguments into the function definition as either mandatory or keyword arguments, they all end up being local variables inside the function. Why does that matter? It means that you can use Python’s locals() function to return a dictionary containing all of those values! This is exactly what I needed. It allows you to let Python verify the spelling of each mandatory and keyword argument name, plus it allows you to specify default values for keyword parameters, and it puts all of the arguments into one nice data structure (i.e., dictionary). Plus, it’s very easy to do.

>> def foo(x, y, a=1, b=2, c=3, d=4):
       print locals()
>> foo('hi', 'bye', c=10)
{'a': 1, 'c': 10, 'b': 2, 'd': 4, 'y': 'bye', 'x': 'hi'}

Now the arguments can all be processed easily. However, there is one thing to watch out for in method definitions.

>> class Test:
>>     def foo(self, x, y, a=1, b=2, c=3, d=4):
>>         print locals()
>> Test().foo('hi', 'bye', c=10)
{'a': 1, 'c': 10, 'b': 2, 'd': 4, 'self': <__main__.Test instance at 0x100493b90>, 'y': 'bye', 'x': 'hi'}

Notice that since self is specified in the method definition, it also shows up in the locals() dictionary. You can use Python’s del to delete that extra key (using a copy of locals(), of course).

args = locals().copy()
del args['self']

Now we have a way of letting Python do its work to validate arguments, and we can easily process the arguments as a single chunk of data.