Hack w/ Limbic Media

Interactive Electronica and IoT

Visit Limbic Media

hack.limbicmedia.ca was created by the developers and engineers at Limbic Media to share their knowledge and experiences with DIY and hacker community-at-large.

by Steven Bjornson

Function Chaining in Python

Let me start with a little example. If you have a bunch of operations to perform on an input that you want to chain together (i.e. output of operation 1 into input of operation 2, ...), it is fairly easy to write a bunch of functions and then call them in order (perhaps even to write another function to call all of these functions in order). But, what if you want to make a bunch of different variations to this chain such as the order of the operations (functions which are not commutative; a - > b != b -> a), add new functions to the chain, or, for testing, create a bunch of chains with a variety of different input parameters?

Fortunately, Python makes this easy (thanks Dict()).

Let's see some code:

First, we'll make a couple different functions:

def multiply(input_, **kwargs):  
    return input_ * kwargs.get('value', 1)
def power(input_, **kwargs):  
    return input_ ** kwargs('pow', 2)

The above functions have the same interface: input_ which in our case will be the input signal and **kwargs which is a fancy way of sending an arbitrary amount of other arguments (in the form of a dictionary; for example, you could call multiply(input_, an_argument=10, whatever= 'yes!', any_other_argument = 2)).

Furthermore, you can see that each of these functions returns their result. This is important because we're going to pass the output of functions into other functions.

Next, we'll put the references to these functions into a list (with some arguments!). I'll call this composing a signal chain.

signal_chain = [  
        {'function': multiply, 'value': 2},
        {'function': power, 'pow': 2} ]

You'll notice there are two dictionaries packed into a list. This allows us to iterate through the list and to handle each operation in sequence. The 'function' dictionary key has the function pointer. The second key in each dictionary acts as our input arguments for the functions that will be called.

Next, let's make a function to handle chaining these operations together.

def chain(input_, operations):  
    for operation in operations:
        input_ = operation['function'](input_, **operation)

    return input_

Hopefully this doesn't seem too weird.

The first statement (for operation in operations:) takes each element in the list (in order) and assigns it to the variable operation. This dictionary (because each element in our list is a dictionary) is then used in the next line.

Here's the tricky part, the 'function' key in the operation dictionary returns a function, this is passed two arguments: (input_, **operation)

input_ is the value we're getting the function to manipulate and return.

**operation is a funny thing: first, we're actually sending our temporary dictionary (operation, the dictionary containing our function and parameters). We can send the whole dictionary without issue because the ** before the dictionary name makes the function's second argument properly parse the dictionary. And so, because the second argument for all of our functions is **kwargs, which allows an arbitrary number of input, we don't have to worry that the dictionary has extra information not used by the function.

To illustrate this, take another look at the power function: because kwargs is in fact the dictionary we put in, we can use any key we like contained. So, the input_ is taken to the power of the value contained at kwargs['pow']. Since the other keys are not called in the function, the function does not care that kwargs contains a reference to the function itself.

In action:

signal_chain1 = [  
        {'function': multiply, 'value': 2},
        {'function': power, 'pow': 2} ]
signal_chain2 = [  
        {'function': power, 'pow': 2},
        {'function': multiply, 'value': 2}]

out1 = chain(3, signal_chain1)  
out2 = chain(3, signal_chain2)

print 'out1', out1  
print 'out2', out2

out1 36  
out2 18  

Need help with a similar project?

Innovative Digital Art

Limbic Media

Consulting Engineering

Limbic Consulting