Skip to main content

Functions

Function Basics

Simple Function Definition

# Basic function
def greet():
return "Hello, World!"

# Function with parameters
def greet_person(name):
return f"Hello, {name}!"

# Function with return value
def add(x, y):
return x + y

# Function with docstring
def calculate_area(radius):
"""
Calculate the area of a circle.

Args:
radius (float): The radius of the circle

Returns:
float: The area of the circle
"""
return 3.14159 * radius ** 2

# Usage
result = greet()
message = greet_person("Alice")
sum_result = add(5, 3)
area = calculate_area(2.5)

Function Parameters and Arguments

# Required parameters
def divide(dividend, divisor):
return dividend / divisor

# Default parameters
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"

# Keyword arguments
def create_user(name, age, email="", active=True):
return {
"name": name,
"age": age,
"email": email,
"active": active
}

# Usage
result = divide(10, 2)
msg1 = greet("Alice") # Uses default greeting
msg2 = greet("Bob", "Hi") # Custom greeting
msg3 = greet(greeting="Hey", name="Charlie") # Keyword arguments

user1 = create_user("Alice", 25)
user2 = create_user("Bob", 30, email="bob@example.com")
user3 = create_user("Charlie", 35, active=False, email="charlie@example.com")

Variable Arguments (*args and **kwargs)

*args (Variable Positional Arguments)

# Accept any number of positional arguments
def sum_all(*args):
return sum(args)

def print_items(*items):
for item in items:
print(item)

def multiply_all(*numbers):
result = 1
for num in numbers:
result *= num
return result

# Usage
total = sum_all(1, 2, 3, 4, 5) # Returns 15
print_items("apple", "banana", "orange") # Prints each item
product = multiply_all(2, 3, 4) # Returns 24

# Pass list as *args
numbers = [1, 2, 3, 4, 5]
total = sum_all(*numbers) # Unpacks the list

**kwargs (Variable Keyword Arguments)

# Accept any number of keyword arguments
def create_profile(**kwargs):
profile = {}
for key, value in kwargs.items():
profile[key] = value
return profile

def print_info(**info):
for key, value in info.items():
print(f"{key}: {value}")

# Usage
profile = create_profile(name="Alice", age=25, city="New York")
print_info(name="Bob", job="Engineer", salary=75000)

# Pass dictionary as **kwargs
user_data = {"name": "Charlie", "age": 30, "email": "charlie@example.com"}
profile = create_profile(**user_data)

Combined Parameters

# Proper parameter order: positional, *args, keyword, **kwargs
def complex_function(required, default="value", *args, keyword_only=None, **kwargs):
print(f"Required: {required}")
print(f"Default: {default}")
print(f"Args: {args}")
print(f"Keyword only: {keyword_only}")
print(f"Kwargs: {kwargs}")

# Usage
complex_function("test", "custom", 1, 2, 3, keyword_only="special", extra="data")

Lambda Functions

Basic Lambda Functions

# Simple lambda
square = lambda x: x ** 2
add = lambda x, y: x + y
is_even = lambda n: n % 2 == 0

# Usage
result = square(5) # 25
sum_val = add(3, 4) # 7
even = is_even(8) # True

# Lambda with conditional
max_val = lambda x, y: x if x > y else y
abs_val = lambda x: x if x >= 0 else -x

Lambda with Built-in Functions

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# With map()
squares = list(map(lambda x: x ** 2, numbers))
doubled = list(map(lambda x: x * 2, numbers))

# With filter()
evens = list(filter(lambda x: x % 2 == 0, numbers))
greater_than_5 = list(filter(lambda x: x > 5, numbers))

# With sorted()
words = ["apple", "pie", "banana", "cherry"]
sorted_by_length = sorted(words, key=lambda x: len(x))
sorted_by_last_char = sorted(words, key=lambda x: x[-1])

# With reduce()
from functools import reduce
product = reduce(lambda x, y: x * y, numbers)
max_value = reduce(lambda x, y: x if x > y else y, numbers)

Lambda in Data Structures

# Lambda in list of functions
operations = [
lambda x: x + 1,
lambda x: x * 2,
lambda x: x ** 2
]

# Apply all operations
number = 5
results = [op(number) for op in operations] # [6, 10, 25]

# Lambda in dictionary
calculators = {
'add': lambda x, y: x + y,
'subtract': lambda x, y: x - y,
'multiply': lambda x, y: x * y,
'divide': lambda x, y: x / y if y != 0 else None
}

# Usage
result = calculators['add'](10, 5) # 15

Closures

Basic Closures

# Closure example
def outer_function(x):
# This is a closure
def inner_function(y):
return x + y # x is captured from outer scope
return inner_function

# Usage
add_10 = outer_function(10)
result = add_10(5) # Returns 15

# Multiple closures
add_5 = outer_function(5)
add_20 = outer_function(20)

print(add_5(3)) # 8
print(add_20(3)) # 23

Practical Closure Examples

# Counter closure
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter

# Usage
counter1 = make_counter()
counter2 = make_counter()

print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 1 (separate counter)

# Multiplier closure
def make_multiplier(factor):
def multiplier(number):
return number * factor
return multiplier

# Usage
times_2 = make_multiplier(2)
times_3 = make_multiplier(3)

print(times_2(5)) # 10
print(times_3(5)) # 15

# Configuration closure
def make_validator(min_length, max_length):
def validate(text):
return min_length <= len(text) <= max_length
return validate

# Usage
username_validator = make_validator(3, 20)
password_validator = make_validator(8, 50)

print(username_validator("abc")) # True
print(password_validator("short")) # False

Decorators

Basic Decorators

# Simple decorator
def my_decorator(func):
def wrapper():
print("Before function call")
result = func()
print("After function call")
return result
return wrapper

# Using decorator
@my_decorator
def say_hello():
return "Hello!"

# Equivalent to: say_hello = my_decorator(say_hello)
result = say_hello()

Decorators with Arguments

# Decorator for functions with arguments
def debug_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper

@debug_decorator
def add(x, y):
return x + y

@debug_decorator
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"

# Usage
result = add(5, 3)
message = greet("Alice", greeting="Hi")

Decorator with Parameters

# Parameterized decorator
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(3)
def say_hello():
print("Hello!")

# Usage
say_hello() # Prints "Hello!" three times

# Timer decorator
import time
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper

@timer
def slow_function():
time.sleep(1)
return "Done"

Built-in Decorators

# @property decorator
class Circle:
def __init__(self, radius):
self._radius = radius

@property
def radius(self):
return self._radius

@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value

@property
def area(self):
return 3.14159 * self._radius ** 2

# Usage
circle = Circle(5)
print(circle.radius) # 5
print(circle.area) # 78.54
circle.radius = 10 # Uses setter

# @staticmethod and @classmethod
class MathUtils:
class_var = "Math Utilities"

@staticmethod
def add(x, y):
return x + y

@classmethod
def get_class_info(cls):
return cls.class_var

# Usage
result = MathUtils.add(5, 3) # No instance needed
info = MathUtils.get_class_info()

Functools Decorators

from functools import wraps, lru_cache

# Preserving function metadata
def my_decorator(func):
@wraps(func) # Preserves original function's metadata
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}")
return func(*args, **kwargs)
return wrapper

@my_decorator
def example_function():
"""This is an example function."""
return "Hello"

print(example_function.__name__) # "example_function"
print(example_function.__doc__) # "This is an example function."

# @lru_cache for memoization
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)

# Usage
print(fibonacci(10)) # Cached for better performance

Higher-Order Functions

Functions that Return Functions

# Function factory
def create_operation(operation):
def add(x, y):
return x + y
def multiply(x, y):
return x * y
def subtract(x, y):
return x - y

operations = {
'add': add,
'multiply': multiply,
'subtract': subtract
}

return operations.get(operation, lambda x, y: None)

# Usage
add_func = create_operation('add')
multiply_func = create_operation('multiply')

result1 = add_func(5, 3) # 8
result2 = multiply_func(4, 6) # 24

# Partial application
def partial_multiply(factor):
def multiply(x):
return x * factor
return multiply

double = partial_multiply(2)
triple = partial_multiply(3)

print(double(5)) # 10
print(triple(5)) # 15

Functions that Take Functions as Arguments

# Function that accepts other functions
def apply_operation(numbers, operation):
return [operation(x) for x in numbers]

def process_data(data, *processors):
result = data
for processor in processors:
result = processor(result)
return result

# Usage
numbers = [1, 2, 3, 4, 5]
squared = apply_operation(numbers, lambda x: x ** 2)
doubled = apply_operation(numbers, lambda x: x * 2)

# Chain processing
def uppercase(text):
return text.upper()

def add_exclamation(text):
return text + "!"

def add_prefix(text):
return "Message: " + text

result = process_data("hello world", uppercase, add_exclamation, add_prefix)
# Result: "Message: HELLO WORLD!"

Scope and Namespaces (LEGB Rule)

LEGB Rule Explained

# L - Local scope
# E - Enclosing scope
# G - Global scope
# B - Built-in scope

# Global variable
global_var = "I'm global"

def outer_function():
# Enclosing scope
enclosing_var = "I'm enclosing"

def inner_function():
# Local scope
local_var = "I'm local"

# Accessing variables following LEGB rule
print(local_var) # Local
print(enclosing_var) # Enclosing
print(global_var) # Global
print(len) # Built-in

inner_function()

outer_function()

Global and Nonlocal Keywords

# Global keyword
counter = 0

def increment_global():
global counter
counter += 1

def get_counter():
return counter

# Usage
increment_global()
print(get_counter()) # 1

# Nonlocal keyword
def outer():
x = 10

def inner():
nonlocal x
x += 5
print(f"Inner x: {x}")

inner()
print(f"Outer x: {x}")

outer() # Inner x: 15, Outer x: 15

# Without nonlocal (creates new local variable)
def outer_no_nonlocal():
x = 10

def inner():
x = 20 # Creates new local variable
print(f"Inner x: {x}")

inner()
print(f"Outer x: {x}")

outer_no_nonlocal() # Inner x: 20, Outer x: 10

Namespace Examples

# Module-level namespace
module_var = "Module level"

class MyClass:
# Class namespace
class_var = "Class level"

def __init__(self):
# Instance namespace
self.instance_var = "Instance level"

def method(self):
# Local namespace
local_var = "Local level"

# Accessing different namespaces
print(local_var) # Local
print(self.instance_var) # Instance
print(MyClass.class_var) # Class
print(module_var) # Module

# Usage
obj = MyClass()
obj.method()

# Namespace inspection
print(locals()) # Local namespace
print(globals()) # Global namespace
print(dir()) # Names in current scope

Built-in Functions for Functional Programming

map(), filter(), reduce()

from functools import reduce

# map() - Apply function to each item
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
strings = list(map(str, numbers))

# Multiple iterables
nums1 = [1, 2, 3]
nums2 = [4, 5, 6]
sums = list(map(lambda x, y: x + y, nums1, nums2))

# filter() - Filter items based on condition
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
greater_than_5 = list(filter(lambda x: x > 5, numbers))

# reduce() - Apply function cumulatively
numbers = [1, 2, 3, 4, 5]
sum_all = reduce(lambda x, y: x + y, numbers)
product = reduce(lambda x, y: x * y, numbers)
max_value = reduce(lambda x, y: x if x > y else y, numbers)

zip() and enumerate()

# zip() - Combine multiple iterables
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["New York", "London", "Tokyo"]

# Zip two lists
pairs = list(zip(names, ages))
# [('Alice', 25), ('Bob', 30), ('Charlie', 35)]

# Zip three lists
triplets = list(zip(names, ages, cities))
# [('Alice', 25, 'New York'), ('Bob', 30, 'London'), ('Charlie', 35, 'Tokyo')]

# Unzip
names_back, ages_back = zip(*pairs)

# enumerate() - Add index to iterable
fruits = ["apple", "banana", "orange"]
indexed = list(enumerate(fruits))
# [(0, 'apple'), (1, 'banana'), (2, 'orange')]

# Start from different index
indexed_from_1 = list(enumerate(fruits, start=1))
# [(1, 'apple'), (2, 'banana'), (3, 'orange')]

# Practical usage
for i, fruit in enumerate(fruits):
print(f"{i}: {fruit}")

all(), any(), sum()

# all() - True if all elements are truthy
numbers = [1, 2, 3, 4, 5]
all_positive = all(x > 0 for x in numbers) # True
all_even = all(x % 2 == 0 for x in numbers) # False

# any() - True if any element is truthy
has_even = any(x % 2 == 0 for x in numbers) # True
has_negative = any(x < 0 for x in numbers) # False

# sum() - Sum of all elements
total = sum(numbers) # 15
total_squares = sum(x ** 2 for x in numbers) # 55

# sum() with start value
total_plus_10 = sum(numbers, 10) # 25

# Practical examples
def all_valid_emails(emails):
return all('@' in email for email in emails)

def any_adult(ages):
return any(age >= 18 for age in ages)

def total_price(items):
return sum(item['price'] for item in items)

Function Annotations and Type Hints

Basic Type Hints

# Basic type annotations
def greet(name: str) -> str:
return f"Hello, {name}!"

def add(x: int, y: int) -> int:
return x + y

def divide(x: float, y: float) -> float:
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y

# Optional parameters
def greet_with_title(name: str, title: str = "Mr.") -> str:
return f"Hello, {title} {name}!"

Advanced Type Hints

from typing import List, Dict, Optional, Union, Callable, Tuple, Any

# List and Dict annotations
def process_numbers(numbers: List[int]) -> List[int]:
return [x * 2 for x in numbers]

def get_user_info(user_id: int) -> Dict[str, Any]:
return {"id": user_id, "name": "John", "active": True}

# Optional types
def find_user(user_id: int) -> Optional[Dict[str, str]]:
# Returns None if user not found
return {"name": "John"} if user_id == 1 else None

# Union types
def process_id(user_id: Union[int, str]) -> str:
return str(user_id)

# Callable types
def apply_function(func: Callable[[int], int], value: int) -> int:
return func(value)

# Tuple types
def get_coordinates() -> Tuple[float, float]:
return (10.5, 20.3)

# Multiple return values
def divide_with_remainder(x: int, y: int) -> Tuple[int, int]:
return divmod(x, y)

Type Aliases and Generic Types

from typing import List, Dict, TypeVar, Generic

# Type aliases
UserID = int
UserData = Dict[str, Union[str, int, bool]]

def get_user(user_id: UserID) -> UserData:
return {"name": "John", "age": 30, "active": True}

# Generic types
T = TypeVar('T')

def first_element(items: List[T]) -> T:
return items[0]

def last_element(items: List[T]) -> T:
return items[-1]

# Usage
numbers = [1, 2, 3, 4, 5]
first_num = first_element(numbers) # Type: int

strings = ["apple", "banana", "orange"]
first_str = first_element(strings) # Type: str

Recursive Functions

Basic Recursion

# Factorial
def factorial(n: int) -> int:
# Base case
if n <= 1:
return 1
# Recursive case
return n * factorial(n - 1)

# Fibonacci sequence
def fibonacci(n: int) -> int:
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)

# Usage
print(factorial(5)) # 120
print(fibonacci(6)) # 8

Optimized Recursion

# Memoized fibonacci
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci_memo(n: int) -> int:
if n <= 1:
return n
return fibonacci_memo(n - 1) + fibonacci_memo(n - 2)

# Tail recursion (though Python doesn't optimize it)
def factorial_tail(n: int, accumulator: int = 1) -> int:
if n <= 1:
return accumulator
return factorial_tail(n - 1, n * accumulator)

# Iterative approach for comparison
def fibonacci_iterative(n: int) -> int:
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b

Practical Recursive Examples

# Tree traversal
def traverse_directory(path: str, level: int = 0) -> None:
import os
indent = " " * level
print(f"{indent}{os.path.basename(path)}")

if os.path.isdir(path):
for item in os.listdir(path):
traverse_directory(os.path.join(path, item), level + 1)

# Binary search
def binary_search(arr: List[int], target: int, left: int = 0, right: int = None) -> int:
if right is None:
right = len(arr) - 1

if left > right:
return -1 # Not found

mid = (left + right) // 2

if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search(arr, target, mid + 1, right)
else:
return binary_search(arr, target, left, mid - 1)

# Flatten nested list
def flatten_list(nested_list: List) -> List:
result = []
for item in nested_list:
if isinstance(item, list):
result.extend(flatten_list(item))
else:
result.append(item)
return result

# Usage
nested = [1, [2, 3], [4, [5, 6]], 7]
flat = flatten_list(nested) # [1, 2, 3, 4, 5, 6, 7]

Function Best Practices

Function Design Principles

# Single Responsibility Principle
def calculate_area(radius: float) -> float:
"""Calculate area of a circle."""
return 3.14159 * radius ** 2

def format_area(area: float) -> str:
"""Format area for display."""
return f"Area: {area:.2f} square units"

# Pure functions (no side effects)
def add_numbers(x: int, y: int) -> int:
"""Pure function - same input always produces same output."""
return x + y

# Avoid global state
def process_data(data: List[int], multiplier: int = 2) -> List[int]:
"""Process data without modifying original."""
return [x * multiplier for x in data]

# Clear function names
def is_valid_email(email: str) -> bool:
"""Check if email format is valid."""
return '@' in email and '.' in email

def get_user_by_id(user_id: int) -> Optional[Dict[str, Any]]:
"""Retrieve user data by ID."""
# Implementation here
pass

Error Handling in Functions

def safe_divide(x: float, y: float) -> float:
"""Safely divide two numbers."""
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y

def parse_int_safe(value: str) -> Optional[int]:
"""Parse string to int, return None if invalid."""
try:
return int(value)
except ValueError:
return None

def read_file_safe(filename: str) -> Optional[str]:
"""Read file content safely."""
try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
print(f"File {filename} not found")
return None
except Exception as e:
print(f"Error reading file: {e}")
return None

Performance Considerations

# Use generators for large datasets
def read_large_file(filename: str):
"""Generator for reading large files line by line."""
with open(filename, 'r') as f:
for line in f:
yield line.strip()

# Cache expensive computations
@lru_cache(maxsize=128)
def expensive_computation(n: int) -> int:
"""Cached expensive computation."""
# Simulate expensive operation
import time
time.sleep(0.1)
return n ** 2

# Use list comprehensions for simple operations
def square_numbers(numbers: List[int]) -> List[int]:
"""Square numbers efficiently."""
return [x ** 2 for x in numbers]

# Avoid premature optimization
def find_maximum(numbers: List[int]) -> int:
"""Find maximum using built-in function."""
return max(numbers) # Built-in is usually faster

Quick Reference

Function Syntax Patterns

# Basic function
def func_name(param1, param2="default"):
"""Docstring"""
return result

# Lambda function
lambda x: x * 2

# Decorator
@decorator
def function():
pass

# Generator
def generator():
yield value

# Async function
async def async_func():
await something()

Common Function Patterns

# Factory function
def create_multiplier(factor):
return lambda x: x * factor

# Validation function
def validate_input(value, min_val, max_val):
return min_val <= value <= max_val

# Transformation function
def transform_data(data, transformer):
return [transformer(item) for item in data]

# Callback pattern
def process_with_callback(data, callback):
result = process(data)
callback(result)
return result

This comprehensive guide covers all essential aspects of Python functions, from basic definitions to advanced concepts like decorators, closures, and type hints. Use these patterns and examples as building blocks for your Python programs!