Freitag, 1. Juli 2011

Using Python decorators to work around version incompatibilities

I'm using PyNN to simulate networks of spiking neurons. PyNN ist a "metasimulator" than can operate with several simulator backends, such as NEST, NEURON, or several others. The cool thing is that PyNN also has a backend for the FACETS hardware, which I'm using in a project. I can prototype the simulation in the simulator, and run it on the hardware afterwards, without changing my simulation script.

In theory.

In practice, things are a bit different. The hardware interface works with PyNN version 0.6, but PyNN has progressed towards 0.7 already. The current version of NEST works only with the current development version (0.7+, that is). This caused some headache for me and others developing for the hardware. Update: Some people wondered and asked me why I wouldn't simply use the old version of NEST that works with 0.6. Well, I could, but actually, that version has other bugs which make this solution a no-go.

Fortunately, the API changes between PyNN 0.6 and 0.7 are not so extensive, so one can work around the differences with relatively little code. Still, one wants to have an elegant way of automatically detecting the PyNN version and using the appropriate code automatically.

Python decorators are particularly well suited for that purpose. Python decorators are functions or classes that return a function. Using a decorator, you can check for the PyNN version in the decorator function and return the appropriate function which does what you want in the current PyNN version.

Confused? OK, here's an example: Assume that I want to retrieve the IDs of all cells in a population. In PyNN 0.6 I must use
def get_population_ids_06(pop):
return [id for id in pop.ids()]

while in PyNN 0.7 I can use
def get_population_ids_07(pop):
return [id for id in pop]

Now I want to have my script automatically figure out which function to use based on the PyNN version which is used. And here comes the decorator into play:
def pynn_version_workaround(pop):
if pynn_version.split(' ')[0] == "0.6.0":
return get_population_ids_06
else:
return get_population_ids_07

Now I have simply to define a dummy function which is to be mangled through the decorator:
@pynn_version_workaround
def get_population_ids(pop):
pass

So, calling get_population_ids is actually first calling pynn_version_workaround, which determines the pyNN version and returns the appropriate function, which is then called with the provided arguments.

Nice, isn't it?

Keine Kommentare: