Welcome to the land of contrived examples and imperfect metaphors. Today we will be visiting a store called the five-item store. You’re only allowed to buy five items, no more, no less. The moment we enter the store, a cashier sees us and begins tailing us, and he keeps repeating “give me your items for so that I can make a bill” over and over again, so we just pick up an item and give it to him, and he shrieks and says “give me all five at the same time or none at all.” At this point you’re probably very annoyed, and this is when, a man in a suit introduces himself, says he is a personal shopper and he will deal with the cashier on your behalf, of course it will cost you a little bit (that’s how corporations suck money, kids!). But since you’re so annoyed, you say “okay, fine! I’ll pay the fee”. The personal shopper is jovial, quiet, and helps you to find the items you need to. You pick two boxes of cookies and hand it over to the personal shopper, then you pick three boxes of coffee (a programmer has needs!) and hand it over. Now that you have five items the personal shopper takes all your items and goes the cashier and gets you the bill. You pay and you leave.
At this point you’re probably wondering what was the point of all that, well, you just used a partial function without knowing it. The personal shopper was the partial function. If you are still confused, let’s try to code this. Let’s start with the make_bill()
function. The make_bill()
function must accept five items.
def make_bill(item1: int, item2: int, item3: int, item4: int, item5: int) -> int:
return item1 + item2 + item3 + item4 + item5
We are 90% done with the program. Remember that we passed items to the personal shopper twice, and as I said the personal shopper is your partial function, we need to get a partial version of the function make_bill(). Here’s where it gets a little tricky, especially if you’ve never seen a higher order function. partial()
is a function that takes in another function and returns a partial version of that function. Actually, partial()
takes in a function and fewer than normal arguments for your function and returns a partial version of it, which accepts the remaining arguments and calls the original function with all the arguments. I know that’s a lot to digest, but keep reading. You can import partial from functools.
from functools import partial # Import this
def make_bill(item1: int, item2: int, item3: int, item4: int, item5: int) -> int:
return item1 + item2 + item3 + item4 + item5
Just so that we can run the code, let’s define cookies and coffee.
# partial_functions.py
from functools import partial
cookies = 10 # new
coffee = 15 # new
def make_bill(item1: int, item2: int, item3: int, item4: int, item5: int) -> int:
return item1 + item2 + item3 + item4 + item5
Let’s fire up the REPL. We will run the file with the interactive mode. My file is called partial_functions.py
python -i partial_functions.py
So the first time we passed the personal shopper two boxes of cookies.
>>> personal_shopper = partial(make_bill, cookies, cookies)
So in the above line of code, we are passing the make_bill function to the partial function, with two arguments corresponding to our two boxes of cookies, partial()
function returns a partial version of make_bill()
.
Now, make_bill()
needs three more arguments, before it can be called. And we will pass three boxes of coffee to the personal_shopper
.
>>> bill = personal_shopper(coffee, coffee, coffee)
>>> bill
65
So once you pass in the remaining arguments to personal_shopper()
. It internally calls the make_bill()
function with all five arguments. It’s as if we called make_bill()
directly.
>>> make_bill(cookies, cookies, coffee, coffee, coffee)
65
Another way to think about partial functions, is like a refrigerator that stores arguments to consume at some other point. It’s similar to storing Chicken in the fridge because you can’t cook it right away since you forgot to buy some ingredients that were required for the recipe.
Some More examples.
Let’s look at some more simple examples. So that you get a better feel for how partial()
works.
Let’s write a function that checks if a number is divisible by another number.
def is_divisible(divident, divisor):
return divident % divisor == 0
>>> is_divisible(10,5)
True
>>> is_divisible(10,3)
False
>>>
let’s write another function that checks if a number is_divisible_by_five
. Since we are big on code reuse, we’ll use the is_divisble()
function.
But is_divisble()
takes two arguments, a dividend and a divisor, but we only one of the arguments we want to pass i.e. 5. partial()
to the rescue
is_divisible_by_five = partial(is_divisible, 5)
Now let’s test this out. open the file in interactive mode.
>>> is_divisible_by_five(10)
False
>>> is_divisible_by_five(20)
False
>>> is_divisible_by_five(100)
False
Something is on the right. What went wrong? partial()
applies the arguments in the order the arguments were passed. 5 was passed first, and then the other argument was passed. effectively we called is_divisible(5,10)
and not is_divisible(10,5)
which is what we actually wanted to call. There are two workarounds to this.
- Change the function signature i.e swap the arguments.
- Use keyword arguments.
In this situation, I would prefer keyword arguments. yes, partial can take keyword arguments too. let’s redefine is_divisible()
, and is_divisible_by_five
.
def is_divisible(divident=1, divisor=1):
return divident % divisor == 0
is_divisible_by_five = partial(is_divisible, divisor=5)
Let’s test it out
>>> is_divisible_by_five(20)
True
>>> is_divisible_by_five(10)
True
>>> is_divisible_by_five(100)
True
Isn’t that neat?
A slightly more advanced example.
Let’s say you wanted to print out all the numbers from 1 to 100 that are divisible by 3 and 5. So what we can do is first we can filter out the numbers that are divisible by five, then we can filter out numbers that are divisible by three and then print them.
Let’s use the filter()
function for this. I’m not going to go in detail about the filter function, but essentially it goes through a list(or an iterable) and removes things that don’t fit a specified condition. It filters out things that don’t fit the condition. The general usage is filter(predicate, iterable)
.
Just remember that filter takes in2 arguments. A list is an iterable. A predicate is a question answering function, for e.g is_divisible_by_five()
is a predicate.
So before we implement all that let’s just see how we can use filter.
>>> list(filter(is_divisible_by_five, [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]))
[5, 10, 15]
Instead of hard coding a list, let’s use the range() function instead
>>> list(filter(is_divisible_by_five, range(1,16)))
[5, 10, 15]
Now we need to implement the is_divisible_by_three
function. This is very similar to is_divisible_by_five
I’m going to let you implement it yourself.
We can actually implement the program now and it will look something like this.
>>> list(filter(is_divisible_by_three, (filter(is_divisible_by_five, range(1,101)))))
[15, 30, 45, 60, 75, 90]
While the program works, it looks a little unwieldy, and it’s not particularly composable, what I mean by not composable is that, filtering out all the numbers that are divisible by five can be reused, and the process of filtering can be applied to any iterable and not just range(1,101)
. So let’s use partial()
to make two new functions that can be reused later on.
filter_multiples_of_five = partial(filter, is_divisible_by_five)
filter_multiples_of_three = partial(filter, is_divisible_by_three)
>>> list(filter_multiples_of_three(filter_multiples_of_five(range(1,100))))
[15, 30, 45, 60, 75, 90]
Here’s where things start paying off, supposedly wanted to filter out multiples of two, along with three and five. We just have to write another Filtering function and added to the one above
filter_multiples_of_two = partial(filter, partial(is_divisible, divisor=2))
>>> list(filter_multiples_of_two(filter_multiples_of_three(filter_multiples_of_five(range(1,100)))))
[30, 60, 90]
So in this way you can compose small functions and really build up to something complex.
And if you still don’t get why you would ever use a partial function, that’s fine, that’s okay, even I didn’t realize the purpose for when I first heard it. The most important thing is you just know that it exists, you will find a use for it later on.
Implementing your own version of partial.
If you’re interested in knowing how to implement partial keep reading.
So we will develop this incrementally, in painfully small steps. So we need to have a function named partial ( if you have imported partial, just comment out that line).
def partial():
return None
Okay what else do we know about partial?
- the first argument is a function.
- The next argument(s) Can vary in number, anywhere from zero to whatever. So we need to support a variable number of arguments think
*args
- We musst also support Keyword arguments. Think
**Kwargs
So let’s implement the.
def partial(fn, *args, **kwargs):
return None
So the next thing you know is, partial returns a function and not None
. Let’s fix that, before we can return a function we need to define a function to return. since partial
doesn’t return itself.
def partial(fn, *args, **kwargs):
def partial_fn():
return None
return partial_fn
Now, what do we know about partial_fn
Does this take in a function? No, it takes and the remaining number of arguments. So again we had to support a variable number of arguments, so let’s add that.
def partial(fn, *args, **kwargs):
def partial_fn(*rem_args, **rem_kwargs):
return None
return partial_fn
Now partial_fn
Must return a value and not None
. So what does it return? at this point partial_fn
Takes in the remaining number of arguments, which means we have all arguments to call the original function. does partial_fn
return the original function or Does it call the original function and returns the value of it? think about. It calls the original function. let’s implement that.
def partial(fn, *args, **kwargs):
def partial_fn(*rem_args, **rem_kwargs):
return fn(*args,*rem_args, **kwargs, **rem_kwargs)
return partial_fn
That’s it, it’s that simple. You implemented your own version of partial. Go have fun with it.