Tasks studies - laboratory
Functional programming is a programming style that is based on functions.
A key part of functional programming is higher-order functions.
Higher-order functions take other functions as arguments or return them as results.
def apply_twice(func, arg):
return func(func(arg))
def add_five(x):
return x + 5
print(apply_twice(add_five, 10))
The function
apply_twice
takes another function as an argument and calls it twice inside.
Functional programming aims to use pure functions.
Pure functions have no side effects and return a value that depends only on their arguments.
This is how mathematical functions work: for example, cos(x)
for the same value of x
always returns the same result.
Below are examples of pure and impure functions.
Pure function:
def pure_function(x, y):
temp = x + 2 * y
return temp / (2 * x + y)
Impure function:
some_list = []
def impure(arg):
some_list.append(arg)
The function above is not pure because it modifies the state of the
some_list
.
Using pure functions has both advantages and disadvantages.
Pure functions are:
Creating functions normally (using def
) assigns them to a variable automatically.
This differs from creating other objects (such as strings and integers), which can be created on the fly without assigning them to a variable.
The same is possible for functions, provided they are created using a lambda expression.
Functions created this way are called anonymous functions.
This approach is most commonly used when passing a simple function as an argument to another function.
The syntax is shown in the example below and consists of the keyword lambda
, followed by a list of arguments, a colon, and an expression to compute and return.
def my_func(f, arg):
return (f(arg))
print(my_func(lambda x: 2 * x * x, 5))
Lambda functions are not as powerful as named functions.
They can only perform operations that require a single expression—usually equivalent to a single line of code.
# Named function
def polynomial(x):
return x ** 2 + 5 * x + 4
print(polynomial(-4))
# Lambda function
print((lambda x: x ** 2 + 5 * x + 4)(-4))
Lambda functions can be assigned to variables and used like normal functions.
double = lambda x: x * 2
print(double(7))
However, there is rarely a good reason for this—it’s usually better to define a function using
def
.
map
and filter
FunctionsThe built-in map
and filter
functions are very useful higher-order functions that operate on lists (or other iterable objects).
The map
function takes a function and an iterable as arguments and returns a new iterable with the function applied to each element.
def add_five(x):
return x + 5
nums = [11, 22, 33, 44, 55]
result = list(map(add_five, nums))
print(result)
We could achieve the same result more easily using lambda syntax:
nums = [11, 22, 33, 44, 55]
result = list(map(lambda x: x + 5, nums))
print(result)
To convert the result to a list, we used the
list()
function.
The filter
function iterates through an iterable and removes elements that do not match the predicate (a function that returns a boolean value).
nums = [11, 22, 33, 44, 55]
res = list(filter(lambda x: x % 2 == 0, nums))
print(res)
Similar to
map
, the result must be explicitly converted to a list if you want to print it.
Generators are a type of iterable like lists or tuples.
Unlike lists, they do not allow indexing with arbitrary indices but can still be iterated over using a for
loop.
They can be created using functions and the yield
statement.
def countdown():
i = 5
while i > 0:
yield i
i -= 1
for i in countdown():
print(i)
The
yield
statement is used to define a generator, replacing the return statement of a function to provide results to the caller without destroying local variables.
Since they process one element at a time, generators do not have memory limitations like lists.
In fact, they can be infinite!
def infinite_sevens():
while True:
yield 7
for i in infinite_sevens():
print(i)
In short, generators allow declaring a function that behaves like an iterator, meaning it can be used in a
for
loop.
Finite generators can be converted into lists by passing them as arguments to the list
function.
def numbers(x):
for i in range(x):
if i % 2 == 0:
yield i
print(list(numbers(11)))
Using generators increases efficiency due to lazy (on-demand) value generation, reducing memory usage.
Decorators allow modifying functions using other functions.
This is useful when you need to extend the functionality of functions without modifying them.
def decor(func):
def wrap():
print("==================")
func()
print("==================")
return wrap
def print_text():
print("Hello world!")
decorated = decor(print_text)
decorated()
Python allows function wrapping with a decorator by using the @
symbol before the function definition.
@decor
def print_text2():
print("Spam")
print_text2()
Recursion is a very important concept in functional programming.
The foundation of recursion is self-referencing by calling itself.
A classic example of recursion is computing the factorial of a number.
def factorial(x):
if x == 1:
return 1
else:
return x * factorial(x - 1)
print(factorial(5))
Recursion can also be indirect, where one function calls another, which calls the first function, and so on.
def is_even(x):
if x == 0:
return True
else:
return is_odd(x - 1)
def is_odd(x):
return not is_even(x)
print(is_even(23))
print(is_odd(17))
Sets are data structures similar to lists or dictionaries.
They are created using curly braces {}
or the set()
function.
They allow checking if an item is contained within them.
num_set = {1, 2, 3, 4, 5}
word_set = set(["spam", "eggs"])
print(3 in num_set)
print("spam" not in word_set)
Sets differ from lists in several ways:
To add elements, use add()
.
To remove elements, use remove()
or pop()
.
nums = {1, 2, 1, 3, 4, 5, 1}
print(nums)
nums.add(-7)
nums.remove(3)
print(nums)
itertools
ModuleThe itertools
module is part of Python’s standard library and contains useful functions for functional programming.
takewhile
- Takes elements from an iterable while a predicate function is True
.accumulate
- Returns the running total of values in an iterable.nums = list(accumulate(range(9)))
print(nums)
print(list(takewhile(lambda x: x <= 6, nums)))
2-7 must be added to GitHub.