In our first blog post of functional programing in Python , we talked about the main concepts and showed the most important functional constructs in Python. For this blog post I will assume you already read the first part, because we will go deeper into some concepts now. Bear in mind that all examples that follow will be provided in Python3.
This blog entry has two main goals. The first one is to help you understand what is function composition. In the second goal, we will deal with two of the best Python modules: operator and functools , which can be used to construct better high-order functions and will also help you to do function composition. That way we can easily combine functions we already built, leaving code more declarative. Let’s see how.
Function Composition made easy
Before we start, let me explain a little about function composition. To say it simple it is when your function is composed by other functions internally.
Take a look at the code below:
def clean_and_lower(text): return text.strip().lower() print(clean_and_lower("Anderson ")) # prints Anderson
clean_and_lower is composed by both
lower functions. First the text is striped off blanks and then the result is showed in lower case.
Let’s write an function to help us to do a simple composition:
def compose(f1, f2): return lambda x: f1(f2(x)) # returns a new funcion composed by the functions passed as argument
Now, we can use that function as follows:
clean_and_lower = compose(str.lower, str.strip) # returns the composed function print(clean_and_lower("Anderson ")) # also prints Anderson
Think of it as if we were plaing with Lego. There are blocks that we can put together to do something more complex. By using function composition we can combine a lot of functions into other functions and so, the code becomes more declarative. This is one of the main concepts in functional programming.
Operator: contributing towards the use of high-order functions
The operator module provides a lot of functions to perform basic operations that can be used isolated or combined with other functions.
Check the code below:
result = 1 + 2 print(result) # prints 3 import operator result = operator.add(1, 2) print(result) # prints 3 too, operator.add is only a function that performs addition print('A'+'B') # prints AB print(operator.concat("A", "B")) # prints AB too
There are a lot of other functions like
operator.gt and others. I know that you may be wondering why one would use operator instead of simply using
1 + 2 . I will show you when to use this module and why it’s waaay better to use operator as High-order functions.
Functools, a walk in the park
Let’s start showing the
reduce function, that receives one function and a array of values and combine them together, returning a single value. Remember to import the functools module.
Let’s sum a list of numbers:
print(functools.reduce(lambda a, b: a+b, [3, 5, 2, 1], 0)) # prints 11
In the example above the lambda function receives two values and sums them. The
reduce function iterates the list while applying the function, combining results until a single
11 value is computed.
Look the image below to understand how
Take a look now how we can replace the
lambda function by using the
print(functools.reduce(operator.add, [3, 5, 2, 1], 0)) # prints 11
Easy peasy, right? The code is simpler and more declarative using
Let’s use again with the
print(functools.reduce(operator.concat, ["Reduce", " is", " Awesome!"])) # prints Reduce is Awesome
Other important function is
partial , which is also present in the functools module. The partial function is used to facilitate the composition of other functions.
Check the example below using the
call_me_mr = functools.partial(operator.concat, "Mr ") call_me_mrs = functools.partial(operator.concat, "Mrs ")
partial function receives one function, in the case above the
operator.concat . One second argument is passed, in our case, the value
Mr . In that way we get back another function that expects the second value of the
print(call_me_mr("Anderson")) # prints Mr Anderson print(call_me_mrs("Renata")) # prints Mrs Renata
See? You can interpret
call_me_mr as a partial application of
operator.concat . It’s partial because it’s missing the second argument, which we pass later by calling
Let’s see an example a little bit more interesting of the use of
partial . We’ll combine partial with the
sorted function and the
person = namedtuple('Person', 'name age') p1 = person('Anderson', 29) p2 = person('Renato', 25) p3 = person('Diego', 27) persons = [p1, p2, p3] # a list of persons sort_by_name = functools.partial(sorted, key=lambda t: t.name) # returns one function that order by name sort_by_age = functools.partial(sorted, key=lambda t: t.age) # returns one function that order by age print(sort_by_name(persons)) # prints Anderson, Diego and Renato print(sort_by_age(persons)) # prints Renato, Diego, Anderson
How about replacing those lambda functions for
sort_by_name = functools.partial(sorted, key=operator.attrgetter('name')) sort_by_age = functools.partial(sorted, key=operator.attrgetter('age'))
We get the same result with a more declarative code.
We introduced some intermediate functional programming concepts, as partial and function composition, applying them using Python. Challenge yourself to see in which moments you can use partial, doing function composition. You will see how this is cool. Also check by yourself the functools and operator modules.