Lecture 1: Subtleties of Functional Programming
In this lecture, we will familiarize ourselves with elements of functional programming in Python. We will discuss what constitutes an object, examine how functions operate as first-class objects, and explore higher-order functions.
To conclude, we will delve into important aspects of Python, including the use of functions like map
, filter
, and reduce
, as well as partial
and lambda functions.
How Functions Work
Of course, you're already familiar with what functions are, aren't you? However, there are certain aspects of functions in Python that we don't often deal with, and as a result, they can be forgotten.
Let's clarify what functions are and how they are represented in Python.
Functions as Procedures
This is the aspect of functions we are most familiar with. A procedure is a named sequence of computational steps. Any procedure can be called from anywhere in the program, including from within another procedure or even from itself. There’s not much more to say about this part, so let's move on to the next aspect of functions in Python.
Functions as First-Class Objects
In Python, everything is an object, not just the objects you create from classes. When you create a variable in Python, you're not just assigning a value to the variable; you're establishing a binding between the variable name and an object. Each type of variable has its own methods. For example, an int
variable has a special method called add
.
All these methods can be listed using dir()
.
As we can see, the variable num
has a vast array of methods. This raises the question of how variables can have methods at all. The answer lies in the nature of Python. Variables have methods because they are, in fact, objects in Python.
This applies not only to simple types like int
, float
, and bool
, but also to modules, functions, and classes. All of these are objects with their own methods and attributes. Therefore, in Python, any object can be assigned to a variable or passed as an argument to a function.
This means that in Python, everything is an object:
📌 Numbers
📌 Strings
📌 Containers
📌 Classes (yes, even classes!)
📌 Functions (which is what interests us)
📌 And everything else
The fact that everything is an object opens up many possibilities for us. Functions can be passed to other functions, returned from functions, created on the fly – meaning they are first-class objects. And this is where functional programming comes into play, along with decorators (which we will cover a little later).
Elements of Functional Programming
Python incorporates some concepts from functional languages like Haskell and OCaml. We'll skip the formal definition of a functional language and go straight to two of its characteristics that are also inherent to Python:
- Functions are first-class objects;
- Consequently, the language supports higher-order functions.
If you're familiar with the basics of higher mathematics, then you already know some mathematical higher-order functions, such as the differential operator d/dxd/dxd/dx. It takes a function as input and returns another function, the derivative of the original function.
Higher-order functions in programming work exactly the same way – they either take function(s) as input and/or return function(s).
So, functions can be passed into other functions. They can also be created within other functions.
Since we returned a different function, the variable multiplier
now holds the inner
function.
Let's try to define the inner
function, which will take one argument and always multiply it by the same number that we passed to get_multiplier
. For example, if we pass 2 to get_multiplier
, we get a function that always multiplies the argument passed to it by 2.
This concept is called "closure".
Map
Sometimes it is necessary to apply a function to a set of elements. For these purposes, there are several standard functions. One such function is map
, which takes a function and an iterable object (such as a list) and applies the given function to all elements of the object.
Note the use of the list
function around map
, because map
by default returns a <map object>
(an iterable object).
The same can be done without the map
function, though it will be a bit longer using generator expressions.
This technique might already be familiar to you, but we'll discuss it a bit later nonetheless.
Filter
Another function that is often used in the context of functional programming is the filter
function. The filter
function allows you to filter an iterable based on a condition. It takes a function (the condition) and the iterable itself as input.
Since filter
, like map
, returns a <filter object>
(an iterable object), it's a good idea to wrap it in a list
.
It’s worth noting that although map
and filter
are very powerful, you should avoid overusing them, as this can often make the code less readable.
Anonymous Functions
Anonymous functions are an extremely useful feature that are either overlooked or overused. You'll learn where they shouldn't be used in the lecture on clean code. For now, let's talk about implementing anonymous functions.
Overall, the structure is very simple, as we've illustrated in the picture.
You start by writing the keyword lambda
, followed by the arguments, a colon, and then some expression that will be automatically returned. There's no need to write return
.
Now let's show a few examples of how to use them. We believe there shouldn't be any difficulties here.
A lambda function (anonymous function) is like a regular function but without an identifier.
If we want to pass a small function to `map` that we won’t need again, we can use lambda functions. Lambda allows you to define a function "in place," meaning without the `def` keyword. Let's do the same thing as in the example with `map`, but using `lambda`.
Lambda functions are also convenient to use with filter
.
Reduce
The functools
module allows you to leverage Python's functional features even more effectively.
For example, in recent versions of the language, functools
includes the reduce
function, which allows you to reduce data by applying a function sequentially and remembering the result:
Thus, reduce
multiplies 1 by 2, then multiplies the result by 3, and so on.
And of course, you can achieve the same thing using a lambda function:
Partial
The `partial` method from `functools` allows you to create a new function with some of the arguments of the original function fixed to certain default values. For better understanding, see the example below.
Zip
Another important function is zip
, which allows you to combine two or more iterables. In the following example, we sequentially pair items from num_list
and squared_list
into tuples.
The `zip` function returns a `<zip object>`, so we wrap it in a `list` to see the result.
Additionally, `zip` can be used to create a dictionary from two lists.
Collection Generators Instead of map, filter
Up to this point, we've defined lists in the standard way, but Python offers a more elegant and concise construction for creating lists and other collections, which we've briefly touched on.
For creating a list of squares of numbers, you can use a list comprehension instead of map
.
Let’s explain the structure of this expression:
In square brackets, you essentially write a standard for
loop, but the expression you want to apply is placed before the for
keyword.
List comprehensions (or list generators) allow you to create nested lists with for
loops and other complex expressions. They are generally preferred over traditional loops for performance reasons, as they can be slightly faster. However, overusing them can reduce code readability.
Similarly, you can write a list comprehension with a condition (replacing the filter
function). The syntax is the same, just add the condition at the end.
In a similar way, you can define dictionaries. This can be useful if you need to set default values.
When using list comprehensions with curly braces, if you omit the colon, you'll get a set instead.
Without brackets, a list comprehension returns a generator—a type of object that can be iterated over (generators will be discussed in more detail later).
Comments (0)