Partial Application in Python

Martin McBride
Sep 4, 2020 · 5 min read

Deriving new functions with functools

Image for post
Image for post
Photo by Matthew Henry on Unsplash

Functional programming is a paradigm in which functions form the fundamental building blocks.

It should be no surprise that functional programming includes several techniques for deriving new functions from existing functions. This is analogous to object oriented programming, where classes are the building block, and we have various ways to derive new classes from existing ones (inheritance and composition, for example). In both cases, the aim is the same — to reuse existing code (following the DRY principle, don’t repeat yourself).

Partial application is one such technique.

What is partial application?

Here is a function that returns the value of x, clamped to the range a to b. That is, it returns the value of x unless x is less than a (in which case a is returned), or x is greater than b (in which case b is returned):

def clamp(a, b, x):
return min(b, max(a, x))

The partial method (in the standard functools module) can be used to create a new function, that behaves like clamp, but with some of its arguments already set to fixed values. For example:

from functools import partialclamp_01 = partial(clamp, 0, 1)
print(clamp_01(2)) # Prints 1

Here, clamp_01 is a function that does the same job as clamp, but with parameter a set to 0 and b set to 1. This means that clamp_01 only accepts a single parameter, x, that gets clamped between 0 and 1.

We call this technique partial application, because you can think of it behaving as if the clamp function had been applied to a and b, but not yet applied to the final parameter x. This is sometimes described as binding the function to the values of a and b.

The important thing to notice about partial is that it is a higher order function - a function that operates on other functions. It accepts a function as its first argument, and returns a brand new function as a result. partial itself doesn't do any clamping, it just creates a new clamping function.

Example of using partial

The map function is often used in functional programming, to apply a function to a sequence of values. For example:

s = map(neg, [1, -2, 0 -3, 2])

This code applies the built-in neg function to the list of values, creating a sequence of numbers as a result. The neg function simply negates the value (1 becomes -1, -2 becomes 2, etc). So it will create an output sequence (-1, 2, 0, 3, -2). If you try this code remember that map creates a lazy sequence, so you will need to convert it to a list if you want to print it:

print(list(s))

Now the important thing about map is that the function you pass into it must accept exactly one parameter. For example neg accepts one parameter, neg(x) returns the negative of x.

If we wanted to use map to apply 0 to 1 clamping to a sequence of numbers, we couldn't use clamp(0, 1, x) because it takes 3 parameters (even though 2 of them are constant). That is where partial comes in. We can do this:

map(partial(clamp, 0, 1), [1, -2, 0 -3, 2])

Remember that partial returns a function - in this case a function that behaves like clamp except that a and b are fixed, so the partial function only takes a single parameter x. That is exactly what we need!

Why use partial?

Now you may be thinking that there are other ways of doing this. That is certainly true. For example, you could create a special clamp function like this:

def my_clamp(x):
return clamp(0, 1, x)
map(my_clamp, [1, -2, 0 -3, 2])

Or, if you didn’t want to go to the trouble of creating a named function that you only needed to use once, you could use a lambda:

map(lambda x: clamp(0, 1, x), [1, -2, 0 -3, 2])

There is nothing terribly wrong with either of these solutions — they work, and they aren’t overly complicated, inefficient or messy.

But they are procedural. The definition of the my_clamp function doesn't just request a version of clamp that has preset a and b values. It includes code that specifies how to achieve that. The problem is that a line of procedural code could be doing anything. You have to read and understand the code to be sure it is really doing what it claims to be doing. Sure, it isn't that difficult with a one-line function, but still, every line of procedural code is an accident waiting to happen.

On the other hand, the partial call is declarative. You say exactly what you want, but not how to do it:

partial(clamp, 0, 1)

Here, you are very clearly stating that you want a version of clamp that has its first two parameters set to 0 and 1. The code couldn't possibly mean anything else, and assuming you trust the partial function (which is part of core Python so you probably can) there isn't really anything that could go wrong.

By analogy, if you needed to search for a values a Python list, you probably wouldn’t write your own code to do it. You could if you really wanted to, and it wouldn’t be that difficult. But you wouldn’t because it would be pointless, and random, and anyone maintaining your code would first wonder why you would do such a thing, and then probably read your code very carefully to check that there isn’t some clever reason why you aren’t just using the built in function.

The same is true of partial. If you are using a functional programming paradigm, and you need to do a standard thing to a function, such as obtaining a partial function, it is best to just use the standard functions to do it.

Other uses

Partial application can be applied multiple times, for example:

non_neg = partial(clamp, 0)
...
byte_value = partial(non_neg, 255)

In this case, the first call creates a function non_neg(b, x), which is a variant of clamp that clamps the minimum value to 0 but still allows you to choose an upper limit, b. That means that code using this function can definitely never produce negative output.

At some point later in your code, you can call partial again to create byte_value(x), a function that further limits the values to a maximum value of 255 (as well as the previous minimum of 0).

You can also apply optional parameters using partial:

csv = partial(print, sep=",")
csv(1, 2, 3) # 1,2,3
csv(1, 2, 3, sep = "|") 1|2|3

This example creates a new function csv, that behaves exactly like print except the separator is a comma by default. Notice that the csv function still allows you to override the separator if you wish.

Summary

Functional programming is a useful aspect of the Python language that you can mix and match with procedural code. If you decide to use FP, it is worth knowing some of the standard utility functions that Python provides.

If you found this article useful you might be interested in my ebook Functional Programming in Python.

Originally published at https://pythoninformer.com.

The Startup

Medium's largest active publication, followed by +754K people. Follow to join our community.

Martin McBride

Written by

I am a software developer with over 30 years experience in Java, Python and C++. I write for pythoninformer.com.

The Startup

Medium's largest active publication, followed by +754K people. Follow to join our community.

Martin McBride

Written by

I am a software developer with over 30 years experience in Java, Python and C++. I write for pythoninformer.com.

The Startup

Medium's largest active publication, followed by +754K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store