Discover more from The Python Coding Stack • by Stephen Gruppetta
Python's functools.partial() Lets You Pre-Fill A Function
An exploration of partial() and partialmethod() in functools
Don't you love it when you need to fill in a form and some fields are already filled in?
There are times when you're coding in Python when you need to use a function with the same arguments over and over again. If only you could pre-fill those arguments, like the form!
As it happens, you can. Python's
functools.partial() enables you to "pre-fill" some arguments in a function. In this article, I'll explore how to use
partial(). I'll also look at its close relative,
A quick 'author's note': Some of the articles I'll publish on The Python Coding Stack—such as this one—will be focused deep dives into a specific Python function or class, either from the standard module or from key third-party modules such as NumPy or Matplotlib. Readers will fall into one of three categories: 1. You may already know everything about this topic. 2. You may be entirely new to the topic. Or... 3. You may have used the function or class but haven't yet explored it fully. When I write deep dives, I'm usually in the third category trying to learn more about these tools myself.
When you use the built-in
print() function, the last character printed out is the newline character by default. Let's assume you'd like to use
print() often in a program, but you don't want your output to be on a new line each time. You can use the optional argument
print(). I'll use a short example to demonstrate this:
The output of this code is shown below:
Tails Tails Heads Heads Tails Heads Tails Heads Tails Heads
This code "flips a coin" by generating a random number between 0 and 1 using
random.random() and printing
"Heads" if the number is lower than 0.5 and
"Tails" if it's larger.
The second argument in the two
print() calls replaces the newline character, which is the last character by default, with a space. If you omit this keyword argument, each
print() will start a new line.
If you need to use this often in a program, you may wish to have a version of the
print() function with
end=" " pre-filled. You can use
functools.partial() for this:
The output is similar to the previous example:
Heads Tails Tails Tails Heads Heads Tails Tails Tails Tails
partial() is part of the
functools module. You call
functools.partial() with two arguments:
The name of the function you want to "pre-fill", which is
The keyword argument
end=" ". This is also a valid keyword argument in
partial() returns an object you call
print_on_line. You can use this object to replace the
print() function, as we've done in the
if..else blocks in this code. The call to
print_on_line("Heads") is equivalent to
print("Heads", end=" "). We'll look at what's happening in more detail shortly.
The object returned by
functools.partial() looks and behaves like a function. It's an object of type
partial. We'll continue exploring this in a Console/REPL session:
This confirms that
print_on_line is a
functools.partial object and that it's callable. This is why we can use it in the same way as the function we're "pre-filling".
partial object has three attributes:
Let's see their values in this example:
.func attribute contains a reference to the built-in
.args attribute shows any positional arguments passed to
partial(). In this case, there aren't any since the only additional argument is a keyword argument. We wrote the argument as the named keyword argument
end=" ". If we wrote just
" " instead, the argument would be a positional argument.
We'll look at another example later that will include positional arguments. You can read more about positional and keyword arguments in this article I wrote in the pre-substack era!
The final attribute,
.keywords, shows any keyword arguments passed to
partial(). This is a dictionary with the key
"end" with the empty string
" " as its value.
Any positional and keyword arguments passed to
partial() are automatically passed to the original function. This is why
print_on_line("Heads") is equivalent to
print("Heads", end=" ").
partial() With User-Defined Functions
Let's use another short example to further explore
functools.partial(). This function rolls several dice. The dice can have any number of sides:
This code's two
print() functions output the following lists:
[5, 1] [3, 7, 10, 1]
You'll almost certainly get a different output as the numbers are random. The function
roll_dice() takes two arguments:
max_valuedetermines the number of sides of the dice
number_of_dicedetermines how many dice to roll
I split the list comprehension into multiple lines for display purposes only.
roll_dice(6, 2) rolls two six-sided dice. The output is a list with two values, with the values between 1 and 6. In the second call,
roll_dice(10, 4), there are four dice, each with ten sides.
Now, let's create a
roll_standard_dice, which we can use instead of
roll_dice() for the case when we're using standard six-sided dice:
The output is:
[1, 5, 3, 2] [6, 1]
We passed the function
roll_dice as the first argument to
functools.partial(). The second argument is a positional argument since we only passed the value 6 without using a keyword to name it.
Therefore, the expression
roll_standard_dice(4) is equivalent to calling
roll_dice(6, 4). The positional argument we use in
functools.partial(), which is 6 in this case, is used to "pre-fill" the first positional argument in
roll_dice(). The argument you pass to the
roll_standard_dice(), is passed on to
roll_dice() as its second positional argument. This value is 4 in the first call and 2 in the second.
Let's look at the three attributes of this
partial object. I'm showing the outputs as comments within the code in this case for better clarity:
roll_standard_dice.func references the function we're building on,
roll_dice. The attribute
.args contains a tuple with one value, 6, since we passed one positional argument to
Since no keyword arguments are passed to
.keywords attribute is an empty dictionary.
And if you're playing Monopoly, in which you always roll two six-sided dice, you can create another
partial object. I'm showing the outputs as comments again in this code segment:
We passed two positional arguments to
functools.partial() when creating
roll_monopoly_dice, 6 and 2. Therefore, this
partial object is equivalent to
One Final Look At
Let's look at the signature for
You pass the name of the function you would like to "pre-fill" as the first positional argument in
functools.partial(). The forward slash
/ in the signature forces the first argument to be positional-only.
Then, you can add as many positional arguments as you wish, followed by any number of keyword arguments. You can read more about
**kwargs in this article. The positional arguments are stored in the
.args attribute as a tuple, and the keyword arguments are stored in
.keywords as a dictionary.
Let's go back to the
print() function and create a rather bizarre
partial object from it. The
print() function can take several valid keyword arguments, which include
Try to predict the output before running the code or reading on.
functools.partial() call creating
bizarre_print has five arguments:
functools.partial(), which is assigned to
"Here is a random number"is the second positional argument, but the first one to form part of
The random integer returned by
random.randint()is the third positional argument. This is also assigned to the
The following argument is
sep=" • | • ". This is a keyword argument and is assigned to the
There's one final keyword argument,
end=" <THE END>\n", which is also in
Here's the output from the code above:
Here is a random number • | • 20 • | • Hello • | • Let's try this out <THE END>
We've seen that the
end parameter in
print() determines the last printed character. The
sep argument is used to separate multiple values. Its default value is a space, but we've replaced the default with the string
" • | • ".
bizarre_print("Hello", "Let's try this out") is equivalent to this
The two positional arguments passed in
functools.partial() are passed to
print() first. The positional arguments passed to
bizarre_print() come next in the equivalent
print() call. Finally, there are the two keyword arguments from the
partial object. You could also pass additional keyword arguments in
bizarre_print() if you wish.
Let's look at the
.keywords attributes in
Now we have two positional arguments in
.args and two keyword arguments in
.keywords. These are passed to
print() along with any other positional and keyword arguments in
Does This Work With Methods?
A method is a function. It's a function that's part of a class, but it's still a function. So can we use the same tools to pre-fill arguments in a method? Let's try this out by creating a class and using
functools.partial() on a method. You'll see that we'll encounter a problem:
This raises the following error:
Traceback (most recent call last): File ... line 14, in <module> article.set_substack() TypeError: Article.set_platform() missing 1 required positional argument: 'platform'
The class has a method called
set_platform(). This method sets the platform used to publish the article. But since most of my articles are now on Substack (!), we tried to create a
partial object called
set_substack with the
platform article pre-set to
This raises an error. The first parameter in
self. Therefore, the string
"substack" we used in
functools.partial() is passed to
set_platform() as its first argument. However, the first argument should be a reference to the instance itself. When you call a method in the usual way, such as if you use
article.set_platform(), the reference to the instance is passed to the method automatically. But in this case, we're calling
set_substack is not a method but the object returned by
As a result, the required parameter
platform is unfilled. This leads to the
TypeError. We can try to pass the string
partial() as a keyword argument instead, using
This still raises an error:
Traceback (most recent call last): File ... line 14, in <module> article.set_substack() TypeError: Article.set_platform() missing 1 required positional argument: 'self'
The error message has changed. The parameter
platform is no longer the issue since we passed the value using a keyword argument. Therefore the
platform parameter in
set_platform() has a value assigned to it. However,
self is missing now!
functools module provides us with another tool to replicate the behaviour of
partial() for methods. This is
The output now shows the name of the platform:
We can look at the attributes
.keywords for the
Article.set_substack. The outputs are shown as comments in this example:
.func attribute now references the method bound to the instance
article. We only included one positional argument in
functools.partialmethod(). Therefore, there's a tuple with one item in
.keywords is an empty dictionary.
Just like your pre-filled forms, you can now create "pre-filled" functions or methods using
functools.partialmethod(), which allow you to freeze some arguments. You may wish to do this to avoid repetitive function calls with the same arguments or to simplify function calls with many arguments. In some cases, this makes the code more readable, more maintainable, and less prone to bugs.
Code in this article uses Python 3.11
The Python Coding Stack • by Stephen Gruppetta is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.
Recently published articles on The Stack:
Let The Real Magic Begin • Creating Classes in Python. Year 2 at Hogwarts School of Codecraft and Algorithmancy • Defining Classes • Data Attributes
Sequences in Python. Sequences are different from iterables • Part 2 of the Data Structure Categories Series
Harry Potter and The Object-Oriented Programming Paradigm. Year 1 at Hogwarts School of Codecraft and Algorithmancy • The Mindset
Iterable: Python's Stepping Stones. What makes an iterable iterable? Part 1 of the Data Structure Categories Series
Why Do 5 + "5" and "5" + 5 Give Different Errors in Python? • Do You Know The Whole Story? If
__radd__()is not part of your answer, read on…
The next cohort of The Python Coding Programme starts next week. Live sessions with very small cohorts over 3 weeks, with 90 minutes live on Zoom every day (4 days a week). Each cohort only has 4 participants and there's active mentoring throughout with a private forum for the cohort to continue discussions. Here's some more information about The Python Coding Programme for Beginners.
Most articles will be published in full on the free subscription. However, a lot of effort and time goes into crafting and preparing these articles. If you enjoy the content and find it useful, and if you're in a position to do so, you can become a paid subscriber. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles. Thank you!