Functional Python Magic
Here is a quick note on functional Pythonic beauty!
# A pure function always returns the same output for the same
# input and has no side effects
def multiply(x, y):
return x * y
# A pure function with safe side effects. Returns the same output
# for the same input and does not mutate the input (immutablity)
def pure_double(list):
new_list = list.copy()
# double each element
for i in range(len(new_list)):
new_list[i] *= 2
return new_list
# First-Class functions - functions can be assigned to variables or
# passed as arguments
def greet(name):
return f'Hello {name}'
# First-Class functions. Higher-order function - function that takes
# a function as an argument
def executor(f, name):
# execute the function
return f(name)
print('Higher Order Function:',executor(greet, 'John'))
# First-Class functions - Assign function to a variable
message = greet
# use the function
print('Assign function to a variable:',message('John'))
# lambda functions
double = lambda x: x * 2
# use the function
print('Lambda function',double(5))
# Map, Filter, Reduce
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# Map: Create a new list by applying a function to each item in an iterable
doubled = list(map(lambda x: x * 2, numbers))
print('Map function:',doubled)
# Filter: Create a new list by keeping only the items that pass a test
evens = list(filter(lambda x: x % 2 == 0, numbers))
print('Filter function:',evens)
# Reduce: Apply a function to a sequence of elements and return a single value (Aggregate)
sum = reduce(lambda x, y: x + y, numbers)
print('Reduce function:',sum)
# Decorator - function that takes a function as an argument and
# returns a new function with added functionality
def logit(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
# Use the decorator
@logit
def add(x, y):
return x + y
# Use the decorator
@logit
def sub(x, y):
return x - y
# Use the decorated functions
print('Decorator:',add(2, 3))
print('Decorator:',sub(2, 3))
Intentionally did not add recursion, as in Python, things could get slower with recursion. Explanation:
Recursion in Python, especially in a functional style, can be slower than iterative approaches due to several factors. Each recursive call adds a new frame to the call stack, leading to function call overhead and increased memory usage. Python also has a recursion depth limit to prevent stack overflow errors, which can restrict the use of deep recursion.
Techniques like memoization (caching results of previous calls) and tail call optimization (converting recursion to iteration) can improve performance, but Python doesn't implement tail call optimization by default. Therefore, for performance-critical applications, iterative solutions are often preferred over recursive ones in Python.