Partial functions are used to create new functions from an existing function by pre-filling some of its arguments and thus creating a new version of the original function. This is particularly useful when dealing with functions that require a large number of arguments. Instead of having to define each argument for every call, we can create a specialized version of the function with some arguments already pre-filled. We achieve this primarily using  the partial class from the functools module.

Consider the following example:

ExampleEdit & Run
def power(a, b):
    return a ** b

print(power(4, 2))
copy
Output:
16 [Finished in 0.010566965211182833s]

We can use the partial class to create more specialized versions of the power() function above, as shown below.

ExampleEdit & Run
import functools

def power(a, b):
    return a ** b

square = functools.partial(power, b = 2)
cube = functools.partial(power, b = 3)

print(square(5))
print(cube(5))
copy
Output:
25 125 [Finished in 0.012772988062351942s]

As shown above the partial constructor takes in a function as the required argument, it then takes the arbitrary positional and keyword arguments to pre-fill. The syntax is as follows:

partial(func, *args, **kwargs)
copy
Parameters:

The function returns a callable object which is a specialized application of the original function in which the specified arguments have been pre-filled.

Consider the following example:

ExampleEdit & Run
import functools

#create a specialized version of the builtin sorted() function
abs_sorted = functools.partial(sorted, key = abs)

#use the partial function to sort integers by their absolute value
L = [2, 0, 5, -3, -2, 1, -4, ]

print(abs_sorted(L))
copy
Output:
[0, 1, 2, -2, -3, -4, 5] [Finished in 0.012600438669323921s]

In the above example, we created a version of the builtin sorted() function in which the objects are sorted by their absolute value by default.

partial with other callable objects

In reality, the partial class can be used with any callable object not just freestanding functions. In the following example we use the class to create a specialized version of a class constructor.

ExampleEdit & Run
import functools

class Person:
    def __init__(self, name, country, nationality):
        self.name = name
        self.country = country
        self.nationality = nationality
    
    def info(self):
        return f"""name: {self.name}
country: {self.country}
nationality: {self.nationality}"""

indian = functools.partial(Person, country = 'India', nationality = 'Indian')

p1 = indian('Rahul')

print(p1.info())
copy
Output:
name: Rahul country: India nationality: Indian [Finished in 0.01236449321731925s]

Acquiring original properties

By default, the partial objects do not inherit the __name__  and  __doc__ attributes from the original function which. The two attributes can be really useful especially for debugging purposes.

ExampleEdit & Run
import functools

def power(a, b):
    '''returns a ^ b'''
    return a ** b

#get the docstring
print(power.__doc__, '\n')

square = functools.partial(power, b = 2)

#get the docstring of the partial functions
print(square.__doc__)
copy
Output:
returns a ^ b  partial(func, *args, **keywords) - new function with partial application     of the given arguments and keywords. [Finished in 0.011680945288389921s]

We can use the update_wrapper() utility function from the module, to assign the properties of the original objects to the partial objects.

Syntax:
update_wrapper(wrapper, wrapped, ...)
copy

The function copies the necessary properties from the original callable to the partial object.

ExampleEdit & Run
import functools

def power(a, b):
    '''returns a ^ b'''
    return a ** b

square = functools.partial(power, b = 2)

#assign original properties to the square function
functools.update_wrapper(square, power)

print(square.__name__)
print(square.__doc__)
copy
Output:
power returns a ^ b [Finished in 0.011637354735285044s]