Functional Utilities: map()
, filter()
, reduce()
, any()
, all()
✅ map()
– Apply a Function to Each Element
nums = [1, 2, 3]
squared = list(map(lambda x: x**2, nums))
print(squared) # [1, 4, 9]
✅ filter()
– Extract Elements Based on Condition
nums = [-2, -1, 0, 1, 2]
positives = list(filter(lambda x: x > 0, nums))
print(positives) # [1, 2]
✅ reduce()
– Cumulative Computation (From functools
)
global local variables nested functions
from functools import reduce
nums = [1, 2, 3, 4]
product = reduce(lambda x, y: x * y, nums)
print(product) # 24
✔ Use Case: Aggregation (sum, product, concatenation).
✅ any()
– Checks if At Least One Condition is True
nums = [-1, 0, 2]
print(any(x > 0 for x in nums)) # True
✅ all()
– Checks if All Conditions Are True
nums = [1, 2, 3]
print(all(x > 0 for x in nums)) # True
✅ sorted()
– Sort an Iterable
sorted()
returns a new sorted list from an iterable without modifying the original.
nums = [3, 1, 4, 2]
sorted_nums = sorted(nums)
print(sorted_nums) # [1, 2, 3, 4]
- Supports custom sorting with key
- Use reverse=True for descending order
words = ["banana", "kiwi", "apple"]
sorted_words = sorted(words, key=len) # Sort by length
print(sorted_words) # ['kiwi', 'apple', 'banana']
Scope & Lifetime in Python
✅ Local vs. Global Scope
- Local: Variables inside a function (only accessible within that function).
- Global: Variables declared at the top level of a module (accessible everywhere).
x = 10 # Global variable
def func():
x = 5 # Local variable (does not modify global x)
print(x) # 5
func()
print(x) # 10 (global x remains unchanged)
✅ global
Keyword
x = 10
def update():
global x
x += 5 # Modifies the global variable
update()
print(x) # 15
✅ nonlocal
Keyword (For Nested Functions)
def outer():
x = 10
def inner():
nonlocal x
x += 5
inner()
print(x) # 15
outer()
✅ Variable Shadowing
A local variable with the same name as a global variable "shadows" it inside a function.
x = "global"
def shadow():
x = "local" # This does not change the global x
print(x) # "local"
shadow()
print(x) # "global"
Function Parameters & Arguments
✅ Positional Arguments
def greet(name, age):
print(f"{name} is {age} years old.")
greet("Alice", 25) # Alice is 25 years old.
✅ Keyword Arguments
greet(age=30, name="Bob") # Bob is 30 years old.
✅ Default Values
def greet(name, message="Hello"):
print(f"{message}, {name}!")
greet("Charlie") # Hello, Charlie!
greet("David", "Hi") # Hi, David!
✅ *args
(Variable Positional Arguments)
def add(*numbers):
return sum(numbers)
print(add(1, 2, 3, 4)) # 10
✅ **kwargs
(Variable Keyword Arguments)
def info(**details):
for key, value in details.items():
print(f"{key}: {value}")
info(name="Emma", age=28, city="NY")
# name: Emma, age: 28, city: NY
First-Class Functions in Python
Python treats functions as first-class citizens, meaning they can be assigned to variables, passed as arguments, and returned from functions.
✅ Assigning Functions to Variables
def greet(name):
return f"Hello, {name}!"
say_hello = greet # Assign function to variable
print(say_hello("Alice")) # Hello, Alice!
✅ Passing Functions as Arguments
def shout(text):
return text.upper()
def whisper(text):
return text.lower()
def speak(func, message):
return func(message)
print(speak(shout, "hello")) # HELLO
print(speak(whisper, "HELLO")) # hello
✅ Returning Functions from Functions
def multiplier(factor):
def multiply(number):
return number * factor
return multiply # Returning the inner function
double = multiplier(2) # Create a function that doubles numbers
print(double(5)) # 10
Lambda Expressions in Python
✅ Syntax & Limitations
add = lambda x, y: x + y
print(add(3, 5)) # 8
✅ Use Cases
- Sorting with
lambda
(Custom Key Function)
names = ["Alice", "Bob", "Charlie"]
names.sort(key=lambda name: len(name))
print(names) # ['Bob', 'Alice', 'Charlie']
- Filtering with
filter()
nums = [1, 2, 3, 4, 5]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [2, 4]
- Mapping with
map()
nums = [1, 2, 3]
squared = list(map(lambda x: x**2, nums))
print(squared) # [1, 4, 9]
Decorators – Enhancing Functions Dynamically
✅ Basic Decorator Pattern
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@decorator # Applying the decorator
def say_hello():
print("Hello!")
say_hello()
Output
Before function call
Hello!
After function call
✅ @decorator Syntax (Shortcut for Decorating)
Instead of manually wrapping:
say_hello = decorator(say_hello) # Manual decoration
We use @decorator to apply it directly.
✅ Stacking Multiple Decorators
Decorators are applied from top to bottom.
def uppercase(func):
def wrapper():
return func().upper()
return wrapper
def exclaim(func):
def wrapper():
return func() + "!!!"
return wrapper
@uppercase
@exclaim
def greet():
return "hello"
print(greet()) # HELLO!!!
✅ Using functools.wraps to Preserve Function Metadata
Without wraps, the function name and docstring are lost.
from functools import wraps
def decorator(func):
@wraps(func) # Preserves original function metadata
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper
@decorator
def greet(name):
"""Greets a person."""
return f"Hello, {name}!"
print(greet.__name__) # greet (not wrapper)
print(greet.__doc__) # Greets a person.
✅ Decorators with Arguments
To pass arguments, nest an extra function layer.
def repeat(n):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3) # Runs function 3 times
def say_hello():
print("Hello!")
say_hello()
Closures – Functions That Remember Their Enclosing Scope
A closure is a function defined inside another function that "remembers" variables from its enclosing scope, even after the outer function has finished executing.
✅ Functions That Remember Enclosing Scope
def outer(x):
def inner(y):
return x + y # `inner` remembers `x` from `outer`
return inner
add_five = outer(5) # Returns a function that adds 5
print(add_five(3)) # 8
Even after outer(5) has executed, inner() still remembers x = 5.
✅ Use Cases of Closures
- Delayed Execution (Creating Function Templates)
def multiplier(n):
def multiply(x):
return x * n # `n` is remembered
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
- Encapsulation (Data Hiding Without Classes)
def counter():
count = 0 # Hidden variable
def increment():
nonlocal count # Modify the enclosed `count`
count += 1
return count
return increment
counter1 = counter()
print(counter1()) # 1
print(counter1()) # 2
count
is protected from external access but persists across function calls.
Recursion – Functions Calling Themselves
✅ Recursive Function Structure
def recurse(n):
if n == 0: # Base case
return
recurse(n - 1) # Recursive call
- Base case stops infinite recursion.
- Each call adds a new stack frame, leading to stack overflow if unchecked.
✅ Factorial Using Recursion
def factorial(n):
return 1 if n == 0 else n * factorial(n - 1)
print(factorial(5)) # 120
✅ Fibonacci Using Recursion
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(6)) # 8
Recursive Fibonacci is inefficient; use memoization or iteration for performance.
✅ Tail Recursion (Conceptual, Not Optimized in Python)
Tail recursion eliminates extra stack frames, but Python does not optimize it.
def tail_factorial(n, acc=1):
return acc if n == 0 else tail_factorial(n - 1, acc * n)
print(tail_factorial(5)) # 120
- Python does not optimize tail recursion, so it still consumes stack space.
- Use recursion wisely; prefer iteration for deep recursive problems!
Introspection in Python
Introspection allows examining objects at runtime, including functions, classes, and modules.
✅ Basic Function Introspection
Python functions store metadata in special attributes.
def greet(name: str) -> str:
"""Returns a greeting message."""
return f"Hello, {name}!"
print(greet.__name__) # greet
print(greet.__doc__) # Returns a greeting message.
print(greet.__annotations__) # {'name': , 'return': }
-
__name__
– Function name -
__doc__
– Docstring -
__annotations__
– Type hints
✅ Using the inspect Module for Advanced Inspection
The inspect
module retrieves detailed function metadata.
import inspect
def example(x, y=10):
"""An example function."""
return x + y
print(inspect.signature(example)) # (x, y=10)
print(inspect.getsource(example)) # Function source code
print(inspect.getdoc(example)) # Docstring
print(inspect.getmodule(example)) # Module where it's defined
- inspect.signature(func) – Retrieves function parameters.
- inspect.getsource(func) – Gets function source code.
- inspect.getdoc(func) – Fetches the docstring.
- inspect.getmodule(func) – Returns the module name.
✅ Introspection helps with debugging, metaprogramming, and documentation!