Hi, I'm Daniel Roy Greenfeld, and welcome to my blog. I write about Python, Django, and much more.

Python Partials are Fun!

Thursday, April 24, 2014 (permalink)

Writing reusable code is a good thing, right? The trick is to do so in a way that makes your life and those of others easier, but to do so in a very clear and maintainable way. Recently I've been playing around with Python's functools.partial function, which I've found can help facilitate writing reusable code.

https://pydanny.com/static/partials.png

While the documentation has a nice explanation and demonstration of functools.partial, it's very serious. I've got my own internal version of things which I think is a little more fun.

My Explanation of functools.partial

What functools.partial does is:

  • Makes a new version of a function with one or more arguments already filled in.
  • New version of a function documents itself.

Rather than dive into paragraphs of explanation, I'll use code examples to explain how this works.

My Demonstration of functools.partial

First, let's say we want to create a function that explicitly performs exponentiation. This way we can get the squares, cubes, and other power operations on any number. This duplicates Python's built-in pow() function, but our version has the very nice addition of keyword arguments.

def power(base, exponent):
    return base ** exponent

Now what if we want to have dedicated square and cube functions that leverage the power() function? Of course, we can do it thus:

def square(base):
    return power(base, 2)

def cube(base):
    return power(base, 3)

This works, but what if we want to create 15 or 20 variations of our power() function? What about 1000 of them? Writing that much repetitive code is, needless to say, annoying. This is where partials come into play. Let's rewrite our square and cube functions using partials, and test it for success using py.test:

from functools import partial

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

def test_partials():
    assert square(2) == 4
    assert cube(2) == 8

Whoa! That's awesome. You know what adds to that awesome? Functions created with partial document themselves (to a degree). I'll demonstrate with more tests:

def test_partial_docs():
    assert square.keywords == {"exponent": 2}
    assert square.func == power

    assert cube.keywords == {"exponent": 3}
    assert cube.func == power

Using a loop, let's build and test ten (10) custom power() functions, which I'll call 'power partials' (ahem... I find 'power partials' sounds rather amusing.):

def test_power_partials():

    # List to store the partials
    power_partials = []
    for x in range(1, 11):

        # create the partial
        f = partial(power, exponent=x)

        # Add the partial to the list
        power_partials.append(f)

    # We could just use list comprehension instead of the loop
    # [partial(power, exponent=x) for x in range(1, 11)]


    # Test the first power
    assert power_partials[0](2) == 2

    # Test the fifth power
    assert power_partials[4](2) == 32

    # Test the tenth power
    assert power_partials[9](2) == 1024

A Way to Organize Partials

Lists are great, but sometimes it's nice to have a more legible way of interacting with functions. There are an infinite ways to make this happen, but I like the dot notation of classes. So here is a 'partial structure' class which follows a pattern I think is pretty handy:

# Since I like my article code to work in both Python 2.7 and 3,
#   I'll import the excellent six library to manage the
#   differences between Python versions. Six is available on PyPI
#   at https://pypi.python.org/pypi/six.
from six import add_metaclass

class PowerMeta(type):
    def __init__(cls, name, bases, dct):

        # generate 50 partial power functions:
        for x in range(1, 51):

            # Set the partials to the class
            setattr(
                # cls represents the class
                cls,

                # name the partial
                "p{}".format(x),

                # partials created here
                partial(power, exponent=x)
            )
        super(PowerMeta, cls).__init__(name, bases, dct)

@add_metaclass(PowerMeta)
class PowerStructure(object):
    pass

Okay, let's test our PowerStructure class as an instantiated PowerStructure:

def test_power_structure_object():
    p = PowerStructure()

    # 10 squared
    assert p.p2(10) == 100

    # 2 to the 5th power
    assert p.p5(2) == 32

    # 2 to the 50th power
    assert p.p50(2) == 1125899906842624

Looks good, right? But wait, there's more!

Thanks to the power of metaclasses, we don't need to instantiate the PowerStructure class!

def test_power_structure_class():
    # Thanks to the power of metaclasses, we don't need to instantiate!

    # 10 squared
    assert PowerStructure.p2(10) == 100

    # 2 to the 5th power
    assert PowerStructure.p5(2) == 32

    # 2 to the 50th power
    assert PowerStructure.p50(2) == 1125899906842624

Source Code

Summary

I've provided some simple examples of how to use functools.partials. I find them really useful for certain tasks, mostly in avoiding repeating myself. Like any coding tool, complex usage can cloak the meaning of code, so be careful and use functools.partials judiciously.

Update: Nick Coghlan reminded me to mention that Python has a pow() built-in.

Update 04/30/2014: Samuel John corrected me on Nick Coghlan's name.


Comments