Python Partial: Code Your Intention

18th February 2018

Of all the functional programming inspired features in Python, partial application must be the best kept secret that you really need to know. Partial application lets you create highly abstract functions and make them more specific for use, pass a function arguments without calling it yet, and so much more.


Python functional iteration

PARTIAL APPLICATION is a much underused tool available in the Python functools library. To demonstrate partial, we’ll use sorting by a key as an example. We’ll look at the obvious pythonic solution, a less verbose non-pythonic solution and compare these to partial. Finally we’ll wrap up with a couple more use-case examples.

Sorting by a key is the kind of operation you might have to repeat, so we should make our own custom version. In this blog I require a function to sort my blog posts by the date published, and I need to use this sort function on different filtered lists of blog posts. So it makes sense to make a function. For our example we’ll use a simpler data structure and we’ll sort a list of tuples. Our simpler example is easier to understand, but the technique really shines with complex examples.

sorted With key

def snd(tup):
    """Second Element"""
    return tup[1]

word = [('l', 3),
        ('e', 2),
        ('o', 5),
        ('h', 1),
        ('l', 3)]

print(sorted(word))
# Output (alphabetical):
##[('e', 2),
## ('h', 1),
## ('l', 3),
## ('l', 3),
## ('o', 5)]

print(sorted(word, key=snd))
# Output (desired order):
##[('h', 1),
## ('e', 2),
## ('l', 3),
## ('l', 3),
## ('o', 5)]

We want a custom sorted

Without Partial

TO CUSTOMISE sorted, we can nest it into another function. The standard, “grunt” way to do this is verbose, which makes a programmer less likely to bother writing it. The lambda version is less verbose, but it’s not considered pythonic.

Lambda expressions to define functions isn’t considered pythonic. There’s an unnecessary cognitive load for Pythonistas unfamiliar with reading this syntax. There must be a better way!

# Standard Python
def snd_sorted(tuple_list):
    """Don't forget your doc string"""
    return sorted(tuple_list, key=snd)

# Less verbose
snd_sorted2 = lambda xs: sorted(xs, key=snd)

snd_sorted(word) == snd_sorted2(word)
## True

The Better Way: Partial

THE PARTIAL application solution encodes what the programmers intention is. snd_sorted and snd_sorted2 are both functions to call sorted with the key kwarg already applied, but neither says that when read in English, both have to be parsed by a reader to see what the authors intention was.

With partial, you can read the code as snd_sorted is the partial application of sorted where the key is snd”. It has captured the authors intention and it makes a powerful tool as we’ll see in the use-case examples. It’s also short and easy to write.

from functools import partial

snd_sorted = partial(sorted, key=snd)

print(snd_sorted(word))
# Output (desired order):
##[('h', 1),
## ('e', 2),
## ('l', 3),
## ('l', 3),
## ('o', 5)]

Use Case 1: String Formatting

# Pie string
pie = "{filling} with {pastry} pastry".format
# Partial pies
steak_pie = partial(pie, filling="Steak")
puff_pie = partial(pie, pastry="puff")
# Make pies
my_pie = steak_pie(pastry="shortcrust")
print(my_pie)
## Steak with shortcrust pastry
cathys_pie = puff_pie(filling="Chicken")
print(cathys_pie)
## Chicken with puff pastry
pork_pie = pie(filling="Pork", pastry="crust")
print(pork_pie)
## Pork with crust pastry

THIS IS in response to a question on stackoverflow, P3trus was asking for a method to apply the arguments to string format at different times in the program. People suggested complex solutions involving classes and some less versatile solutions, but the answer by Saikiran Yerram deserves more upvotes for using partial.

I’ve adapted Saikiran Yerram’s answer to make it more versatile. This is a simple solution that can have the arguments applied in any order. The title of the stackoverflow post is coincidentally: “partial string formatting”: P3trus’ intention is to partially apply the format function. Unlike the other solutions offered, the partial function encodes P3trus’ intention and accomplishes the required task.

Use Case 2: Add Args Without Calling

IN MAKING semantic web applications, I need to return data encoded in different formats depending on the request’s accept headers. Does the request want HTML, JSON-LD, RDF+XML, N3 or Turtle? To do this with Flask, I can use Flask-Accept, which let’s us decorate a route with a mimetype and set a fallback.

The trouble is, I don’t want to write 5 functions to support all those mimetypes. I just want to have one function that accepts a mimetype and returns that serialised data with a fallback to HTML. Flask-Accept doesn’t officially support this, but I looked at their source code and saw the “dictionary of functions” pattern.

# Flask-Accept makes this dictionary from decorators, values are functions:
accept_handlers = {
    'application/xml+rdf': get_xml_rdf,
    'application/ld+json': get_ld_json,
    # and the rest ...
}

# Later called in this way (line 27 in commit fc99339 on 18 Nov 2015)
accept_handlers[mimetype](*args, **kwargs)                   

In Python, functions are first-class, which is what permits this approach. What I need to do is adapt this so that the functions take the mimetype as an argument.

Trouble is, I can’t change Flask-Accept to always pass the mimetype as an argument. This is where partial comes to the rescue.

RDF_MIMETYPES = {
    "application/ld+json": "json-ld",
    "application/rdf+xml": "pretty-xml",
    "text/n3": "n3",
    "application/x-turtle": "turtle",
    "text/turtle": "turtle"
    }

@app.route("/")
@accept_fallback
def page():
    return render_template(
        "page.html",
        content=get_content("page")
    )

page.accept_handlers = {
    mimetype: partial(page_rdf, mimetype) \
    for mimetype in RDF_MIMETYPES
}

I can partially apply the mimetype arg to the page_rdf function and use that as the values for accept_handlers. RDF_MIMETYPES has been borrowed from my model serialisation.

Now I can return 5 different mimetypes from one URL, depending on the accept headers, and it only took a dictionary generator expression, one decorator, and my page_rdf function to get it.

Conclusion

I’VE OFFERED two use-cases where partial provides a working, concise and versatile solution. However, I think its biggest benefit is that it clearly encodes the authors intention. It also allows higher levels of abstraction and code re-use, but I’ve not talked about that here because I believe those benefits will follow as you begin to see useful applications of partial all over your code.

If you are just starting out in functional programming and are looking to include functional techniques into your current work, partial is an excellent tool that you can import and start playing with immediately. Even if you’re not particularly interested in functional programming, partial will still be a very useful tool.

Until next time, happy coding.
Paul

Rabbit Dye Box Trick

Post Tags


All Tags



LIFE IS better when we share.


Comments

JOIN THE conversation: awesome comments.