Functional Programming
Functional programming is a programming style where functions are treated as first-class citizens. This means functions can be assigned to variables, passed as arguments, and returned from other functions. Functional programming focuses on writing clean and concise code that avoids changing state or mutating data.
Key Features of Functional Programming
- First-Class Functions:
- Functions can be treated like variables.
- They can be passed as arguments and returned from other functions.
- Pure Functions:
- A function is "pure" if it doesn’t modify external state and always produces the same output for the same input.
- Immutability:
- Data remains unchanged. Instead of modifying data, new copies are created.
- Higher-Order Functions:
- Functions that can take other functions as arguments or return functions as results.
- Use of Lambda Functions:
- Anonymous, inline functions.
- Built-in Functional Tools:
- Python provides built-in functions like map(), filter(), and reduce() for functional programming.
Examples of Functional Programming in Python
1. First-Class Functions
def square(x): return x * x def apply_function(func, value): return func(value) result = apply_function(square, 5) print(result) # Output: 25
2. Pure Functions
def add_numbers(a, b): return a + b # Pure function, no external state is modified print(add_numbers(3, 4)) # Output: 7
3. Higher-Order Functions
A higher-order function takes another function as input.
def double(x): return x * 2 def apply_to_list(func, numbers): return [func(n) for n in numbers] result = apply_to_list(double, [1, 2, 3, 4]) print(result) # Output: [2, 4, 6, 8]
4. Lambda Functions
Lambda functions are small anonymous functions defined using the lambda keyword.
# Regular function def add(a, b): return a + b # Lambda function (same functionality) add = lambda a, b: a + b print(add(3, 5)) # Output: 8
5. Using map(), filter(), and reduce()
1. map(): Applies a function to all items in an iterable.
numbers = [1, 2, 3, 4] squares = map(lambda x: x * x, numbers) print(list(squares)) # Output: [1, 4, 9, 16]
2. filter(): Filters items in an iterable based on a condition.
numbers = [1, 2, 3, 4] even_numbers = filter(lambda x: x % 2 == 0, numbers) print(list(even_numbers)) # Output: [2, 4]
3. reduce(): Reduces an iterable to a single value (from functools module).
from functools import reduce numbers = [1, 2, 3, 4] total = reduce(lambda x, y: x + y, numbers) print(total) # Output: 10
Benefits of Functional Programming
- Readable and concise code: Focuses on "what" to do rather than "how" to do it.
- Easier to debug and test: Pure functions are predictable.
- No side effects: Immutability reduces bugs caused by data mutations.
When to Use Functional Programming?
Functional programming is ideal for:
- Data processing pipelines.
- Code that prioritizes immutability and pure functions.
- Scenarios where reusable and composable functions are required.
Closures
- A closure is a function that remembers the variables from its enclosing scope even after the scope has finished execution.
- Used to encapsulate logic and maintain state.
Example:
def outer_function(message): def inner_function(): print(message) # Remembers 'message' from outer_function return inner_function closure = outer_function("Hello, World!") closure() # Output: Hello, World!
Decorators
- A decorator is a function that takes another function as input and extends its behavior without modifying the original function.
- Used for logging, authentication, performance monitoring, etc.
Example:
def decorator_function(original_function): def wrapper_function(): print("Wrapper: Before the function call") original_function() print("Wrapper: After the function call") return wrapper_function @decorator_function def say_hello(): print("Hello!") say_hello() # Output: # Wrapper: Before the function call # Hello! # Wrapper: After the function call
Generators
- A generator is a special type of function that produces items one at a time using the yield keyword.
- It does not store the entire result in memory, making it memory-efficient.
Example:
def generate_numbers(): for i in range(5): yield i # Yield one number at a time gen = generate_numbers() for num in gen: print(num) # Output: 0, 1, 2, 3, 4
yield
- The yield keyword is used to produce a value in a generator function and pause the function’s execution.
- The function can resume execution where it left off.
Example:
def countdown(n): while n > 0: yield n n -= 1 for num in countdown(3): print(num) # Output: 3, 2, 1
itertools Module for Advanced Iterators
- The itertools module provides advanced iterator tools to handle complex data-processing tasks efficiently.
- It includes functions for infinite iteration, permutations, combinations, and more.
Common Functions in itertools:
1. count(): Generates an infinite sequence of numbers.
from itertools import count for i in count(5): if i > 10: break print(i) # Output: 5, 6, 7, 8, 9, 10
2. cycle(): Cycles through an iterable indefinitely.
from itertools import cycle for i, item in enumerate(cycle("AB")): if i > 3: break print(item) # Output: A, B, A, B
3. chain(): Combines multiple iterables into one.
from itertools import chain for item in chain([1, 2], ['a', 'b']): print(item) # Output: 1, 2, a, b
4. combinations(): Generates all possible combinations of a given length.
from itertools import combinations for combo in combinations([1, 2, 3], 2): print(combo) # Output: (1, 2), (1, 3), (2, 3)
5. product(): Generates a cartesian product of input iterables.
from itertools import product for prod in product([1, 2], ['a', 'b']): print(prod) # Output: (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')