Python Functions Tutorial: From Basics to Lambda Functions for Beginners
Complete beginner's guide to Python functions with practical examples. Learn function basics, parameters, lambda functions, decorators, and map/filter/reduce for data science and machine learning applications

When Functions Finally Made Sense
I remember staring at Python code filled with functions, wondering why people kept breaking everything into tiny pieces. "Why not just write it all in order?" I thought. Then I tried building my first data science projectโa simple house price predictor with data preprocessing, feature engineering, and model training.
My code quickly became a 500-line mess that I couldn't debug. Every time I wanted to change one thing, I broke three others. That's when functions clicked for me.
Functions are like LEGO blocksโsmall, reusable pieces that you can combine in endless ways. Once I learned to think in functions, my code became cleaner, easier to test, and actually reusable across projects.
In this beginner-friendly guide, I'll walk you through Python functions from the ground up, with practical examples that show why they're essential for data science and machine learning.
Why Functions Matter for Beginners
Before diving into syntax, let's understand why functions are crucial:
Real-World Analogy
Think of functions like recipes:
- Recipe name: Function name (
def bake_cookies()) - Ingredients: Function parameters (
sugar, flour, eggs) - Instructions: Function body (the code inside)
- Result: Return value (delicious cookies!)
Just like you don't rewrite the cookie recipe every time you bake, you don't rewrite codeโyou call the function!
Why Functions Are Essential
Without functions:
# Messy, repetitive code
data1 = [1, 2, 3, 4, 5]
sum1 = 0
for num in data1:
sum1 += num
average1 = sum1 / len(data1)
print(f"Average: {average1}")
data2 = [10, 20, 30]
sum2 = 0
for num in data2:
sum2 += num
average2 = sum2 / len(data2)
print(f"Average: {average2}")
With functions:
# Clean, reusable code
def calculate_average(data):
"""Calculate average of a list of numbers."""
return sum(data) / len(data)
print(f"Average: {calculate_average([1, 2, 3, 4, 5])}")
print(f"Average: {calculate_average([10, 20, 30])}")
See the difference? Functions make your code DRY (Don't Repeat Yourself)!
Function Basics: Your First Function
Let's start with the simplest possible function:
Anatomy of a Function
def greet():
"""This is a docstring - it describes what the function does."""
print("Hello, World!")
# Call the function
greet() # Output: Hello, World!
Breaking it down:
def- Python keyword that says "I'm defining a function"greet- The function name (use descriptive names!)()- Parentheses hold parameters (empty here):- Colon starts the function body- Indented code - The function's instructions
greet()- How you call (execute) the function
Functions with Return Values
Most useful functions return a result:
def add_numbers(x, y):
"""
Add two numbers and return the result.
Args:
x (float): First number
y (float): Second number
Returns:
float: Sum of x and y
"""
result = x + y
return result
# Use the returned value
answer = add_numbers(5, 3)
print(f"5 + 3 = {answer}") # Output: 5 + 3 = 8
# Direct use in expressions
total = add_numbers(10, 20) + add_numbers(5, 15)
print(f"Total: {total}") # Output: Total: 50
Key insight: return sends a value back to where the function was called. Without return, a function returns None.
Function Parameters: Making Functions Flexible
Parameters make functions adaptable to different situations.
Positional Parameters
The most basic typeโorder matters!
def describe_pet(animal_type, pet_name):
"""Display information about a pet."""
print(f"I have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name}.")
# Order matters!
describe_pet("dog", "Buddy")
# Output:
# I have a dog.
# My dog's name is Buddy.
describe_pet("Buddy", "dog") # Wrong order!
# Output:
# I have a Buddy.
# My Buddy's name is dog.
Default Parameters
Give parameters default values for common use cases:
def calculate_power(base, exponent=2):
"""
Calculate base raised to exponent.
Default exponent is 2 (square).
"""
return base ** exponent
# Use default (square)
print(calculate_power(5)) # 25
# Provide custom exponent (cube)
print(calculate_power(5, 3)) # 125
# Named arguments for clarity
print(calculate_power(base=2, exponent=8)) # 256
Best practice: Put default parameters after required ones!
# Good
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Bad (will cause error)
def greet(greeting="Hello", name): # SyntaxError!
return f"{greeting}, {name}!"
Real ML Example: Data Normalization
Here's a practical function data scientists use all the time:
def normalize_data(values, method="min-max"):
"""
Normalize data using specified method.
Args:
values (list): Data to normalize
method (str): "min-max" or "z-score"
Returns:
list: Normalized values
"""
if method == "min-max":
# Scale to 0-1 range
min_val = min(values)
max_val = max(values)
return [(x - min_val) / (max_val - min_val) for x in values]
elif method == "z-score":
# Standardize to mean=0, std=1
mean = sum(values) / len(values)
variance = sum((x - mean) ** 2 for x in values) / len(values)
std_dev = variance ** 0.5
return [(x - mean) / std_dev for x in values]
# Test data
data = [10, 20, 30, 40, 50]
# Min-max normalization (default)
normalized = normalize_data(data)
print(f"Min-max: {[round(x, 2) for x in normalized]}")
# Output: Min-max: [0.0, 0.25, 0.5, 0.75, 1.0]
# Z-score normalization
standardized = normalize_data(data, method="z-score")
print(f"Z-score: {[round(x, 2) for x in standardized]}")
# Output: Z-score: [-1.41, -0.71, 0.0, 0.71, 1.41]
Variable Arguments: Handling Unknown Quantities
Sometimes you don't know how many arguments you'll receive.
*args: Variable Positional Arguments
The *args syntax lets you accept any number of arguments:
def calculate_sum(*numbers):
"""
Sum any number of values.
*numbers becomes a tuple of all arguments.
"""
total = 0
for num in numbers:
total += num
return total
# Works with any number of arguments!
print(calculate_sum(1, 2, 3)) # 6
print(calculate_sum(10, 20, 30, 40, 50)) # 150
print(calculate_sum(5)) # 5
Real ML example:
def calculate_accuracy(*predictions):
"""Calculate average accuracy across multiple model predictions."""
if not predictions:
return 0
return sum(predictions) / len(predictions)
# Compare multiple models
model1_accuracy = 0.85
model2_accuracy = 0.92
model3_accuracy = 0.88
average = calculate_accuracy(model1_accuracy, model2_accuracy, model3_accuracy)
print(f"Average accuracy: {average:.2%}") # 88.33%
**kwargs: Variable Keyword Arguments
**kwargs accepts any number of named arguments:
def configure_model(**config):
"""
Configure ML model with keyword arguments.
**config becomes a dictionary.
"""
print("Model Configuration:")
for key, value in config.items():
print(f" {key}: {value}")
# Pass any configuration options
configure_model(
learning_rate=0.001,
batch_size=32,
epochs=100,
optimizer="adam"
)
# Output:
# Model Configuration:
# learning_rate: 0.001
# batch_size: 32
# epochs: 100
# optimizer: adam
Combining Everything
You can use regular, default, *args, and **kwargs together:
def train_model(model_name, dataset, epochs=10, *layers, **hyperparams):
"""
Train a neural network with flexible architecture.
Args:
model_name (str): Required model name
dataset (str): Required dataset name
epochs (int): Training epochs (default: 10)
*layers: Variable number of layer sizes
**hyperparams: Additional hyperparameters
"""
print(f"Training {model_name} on {dataset}")
print(f"Epochs: {epochs}")
print(f"Architecture: {layers}")
print(f"Hyperparameters: {hyperparams}")
# Example usage
train_model(
"MyNN", # model_name
"MNIST", # dataset
50, # epochs
128, 64, 32, # *layers (3 layers)
learning_rate=0.001, # **hyperparams
optimizer="adam",
dropout=0.5
)
Order rules:
- Regular parameters
- Default parameters
*args**kwargs
Lambda Functions: Quick One-Liners
Lambda functions are anonymous, one-line functions. They're perfect for simple operations.
Basic Syntax
# Regular function
def square(x):
return x ** 2
# Lambda equivalent
square_lambda = lambda x: x ** 2
print(square(5)) # 25
print(square_lambda(5)) # 25
Syntax: lambda parameters: expression
When to Use Lambda Functions
Use lambdas for:
- Simple, one-line operations
- Sorting with custom keys
- Filtering and mapping data
- Callbacks and short-lived functions
Don't use lambdas for:
- Complex logic (use regular functions)
- Multiple statements
- Anything that needs documentation
Practical Lambda Examples
Example 1: Sorting data
# Sort students by grade
students = [
("Alice", 85),
("Bob", 92),
("Charlie", 78),
("David", 95)
]
# Sort by grade (second element)
sorted_students = sorted(students, key=lambda student: student[1], reverse=True)
print(sorted_students)
# Output: [('David', 95), ('Bob', 92), ('Alice', 85), ('Charlie', 78)]
Example 2: Data transformation
# Convert temperatures from Celsius to Fahrenheit
celsius = [0, 10, 20, 30, 40]
fahrenheit = list(map(lambda c: (c * 9/5) + 32, celsius))
print(fahrenheit)
# Output: [32.0, 50.0, 68.0, 86.0, 104.0]
Example 3: Filtering data
# Filter high-confidence predictions
predictions = [0.45, 0.92, 0.67, 0.88, 0.35, 0.95, 0.72]
high_confidence = list(filter(lambda x: x > 0.8, predictions))
print(high_confidence)
# Output: [0.92, 0.88, 0.95]
Map, Filter, and Reduce: Functional Programming
These functions apply operations to collectionsโessential for data processing!
Map: Apply Function to Every Element
# Square all numbers
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]
# Normalize features (scale to 0-1)
features = [25, 50, 75, 100]
normalized = list(map(lambda x: x / 100, features))
print(normalized) # [0.25, 0.5, 0.75, 1.0]
With named functions:
def standardize_text(text):
"""Clean and standardize text."""
return text.strip().lower()
raw_texts = [" Machine Learning ", "DEEP LEARNING", " AI "]
cleaned = list(map(standardize_text, raw_texts))
print(cleaned)
# Output: ['machine learning', 'deep learning', 'ai']
Filter: Select Elements That Meet Criteria
# Filter even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # [2, 4, 6, 8, 10]
# Filter valid scores (0-100)
scores = [85, 92, -5, 78, 105, 67, 110, 89]
valid_scores = list(filter(lambda x: 0 <= x <= 100, scores))
print(valid_scores) # [85, 92, 78, 67, 89]
Real ML example: Feature selection
def is_important_feature(feature):
"""Check if feature has high importance score."""
return feature["importance"] > 0.7
features = [
{"name": "age", "importance": 0.8},
{"name": "income", "importance": 0.9},
{"name": "height", "importance": 0.3},
{"name": "education", "importance": 0.85},
]
important = list(filter(is_important_feature, features))
feature_names = [f["name"] for f in important]
print(f"Important features: {feature_names}")
# Output: Important features: ['age', 'income', 'education']
Reduce: Combine Elements into Single Value
from functools import reduce
# Sum all numbers
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15
# Find maximum value
numbers = [45, 23, 78, 92, 67]
maximum = reduce(lambda x, y: x if x > y else y, numbers)
print(maximum) # 92
Real ML example: Finding best model
from functools import reduce
models = [
{"name": "Random Forest", "accuracy": 0.85},
{"name": "SVM", "accuracy": 0.92},
{"name": "Neural Network", "accuracy": 0.88},
{"name": "Logistic Regression", "accuracy": 0.79}
]
best_model = reduce(
lambda best, current: current if current["accuracy"] > best["accuracy"] else best,
models
)
print(f"Best model: {best_model['name']} ({best_model['accuracy']:.1%})")
# Output: Best model: SVM (92.0%)
Combining Map, Filter, and Reduce
Create powerful data pipelines:
from functools import reduce
# Raw data: model predictions
predictions = [0.45, 0.92, 0.67, 0.88, 0.35, 0.95, 0.72, 0.41, 0.89]
# Pipeline:
# 1. Filter: Keep high confidence (>0.8)
# 2. Map: Convert to percentages
# 3. Reduce: Calculate average
high_confidence = filter(lambda x: x > 0.8, predictions)
percentages = map(lambda x: x * 100, high_confidence)
average = reduce(lambda x, y: x + y, percentages) / len(list(filter(lambda x: x > 0.8, predictions)))
print(f"Average high-confidence prediction: {average:.1f}%")
# Output: Average high-confidence prediction: 90.6%
Function Decorators: Superpowers for Functions
Decorators modify or enhance functions without changing their code. They're like function wrappers!
Basic Decorator Example
def timing_decorator(func):
"""Measure how long a function takes to run."""
import time
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"โฑ๏ธ {func.__name__} took {end - start:.4f} seconds")
return result
return wrapper
@timing_decorator
def slow_calculation(n):
"""Simulate expensive ML computation."""
total = 0
for i in range(n):
total += i ** 2
return total
# Test it
result = slow_calculation(1000000)
print(f"Result: {result}")
# Output:
# โฑ๏ธ slow_calculation took 0.1234 seconds
# Result: 333332833333500000
How it works:
@timing_decoratoris syntax sugar for:slow_calculation = timing_decorator(slow_calculation)- The decorator wraps the original function with timing logic
Practical ML Decorators
Logging decorator:
def log_function_call(func):
"""Log every function call with arguments."""
def wrapper(*args, **kwargs):
print(f"๐ Calling {func.__name__}")
print(f" Args: {args}")
print(f" Kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f" Result: {result}")
return result
return wrapper
@log_function_call
def train_model(epochs, learning_rate=0.001):
"""Simulate model training."""
return f"Trained for {epochs} epochs with lr={learning_rate}"
# Test it
train_model(100, learning_rate=0.01)
# Output:
# ๐ Calling train_model
# Args: (100,)
# Kwargs: {'learning_rate': 0.01}
# Result: Trained for 100 epochs with lr=0.01
Caching decorator (memoization):
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
"""Calculate nth Fibonacci number (cached)."""
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
# Fast even for large numbers!
print(fibonacci(100)) # Instant due to caching!
Best Practices: Writing Great Functions
1. Use Descriptive Names
# Bad
def calc(x, y):
return x + y
# Good
def calculate_total_price(item_price, tax_rate):
return item_price * (1 + tax_rate)
2. Write Docstrings
def normalize_features(features, method="min-max"):
"""
Normalize numerical features for machine learning.
Args:
features (list): List of numerical values to normalize
method (str): Normalization method - "min-max" or "z-score"
Returns:
list: Normalized feature values
Example:
>>> normalize_features([1, 2, 3, 4, 5])
[0.0, 0.25, 0.5, 0.75, 1.0]
"""
# Implementation here
pass
3. Keep Functions Focused
Bad (does too much):
def process_data(data):
# Load data
# Clean data
# Normalize data
# Train model
# Evaluate model
# Save results
pass # Too many responsibilities!
Good (focused):
def load_data(filepath):
"""Load data from file."""
pass
def clean_data(data):
"""Remove missing values and outliers."""
pass
def normalize_data(data):
"""Scale features to 0-1 range."""
pass
def train_model(data):
"""Train ML model on data."""
pass
4. Validate Inputs
def calculate_accuracy(predictions, actual):
"""Calculate classification accuracy."""
# Validate inputs
if len(predictions) != len(actual):
raise ValueError("Predictions and actual must have same length")
if not predictions or not actual:
raise ValueError("Lists cannot be empty")
# Calculate accuracy
correct = sum(1 for p, a in zip(predictions, actual) if p == a)
return correct / len(predictions)
5. Handle Errors Gracefully
def safe_divide(a, b):
"""
Divide two numbers safely.
Returns:
float or None: Result of division, or None if error
"""
try:
return a / b
except ZeroDivisionError:
print("Error: Cannot divide by zero")
return None
except TypeError:
print("Error: Arguments must be numbers")
return None
print(safe_divide(10, 2)) # 5.0
print(safe_divide(10, 0)) # Error: Cannot divide by zero, returns None
Common Beginner Mistakes
Mistake 1: Forgetting to Return
# Wrong
def add_numbers(x, y):
sum = x + y
# Forgot return!
result = add_numbers(5, 3)
print(result) # None (not 8!)
# Right
def add_numbers(x, y):
return x + y
Mistake 2: Modifying Mutable Default Arguments
# Dangerous!
def add_item(item, items=[]):
items.append(item)
return items
# Unexpected behavior
print(add_item("apple")) # ['apple']
print(add_item("banana")) # ['apple', 'banana'] - Wait, what?
# Safe version
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Mistake 3: Not Handling Edge Cases
# Wrong
def calculate_average(numbers):
return sum(numbers) / len(numbers)
# Crashes on empty list!
calculate_average([]) # ZeroDivisionError!
# Right
def calculate_average(numbers):
if not numbers:
return 0
return sum(numbers) / len(numbers)
Real-World Example: Complete ML Pipeline
Let's put everything together in a practical data science example:
from functools import reduce
def load_data(filepath="data.csv"):
"""Load dataset from file."""
# Simulate loading
return [
{"age": 25, "income": 50000, "purchased": 0},
{"age": 45, "income": 80000, "purchased": 1},
{"age": 35, "income": 60000, "purchased": 0},
{"age": 50, "income": 95000, "purchased": 1},
]
def extract_features(data, feature_names=["age", "income"]):
"""Extract specific features from dataset."""
return [[record[name] for name in feature_names] for record in data]
def normalize_features(features):
"""Normalize features using min-max scaling."""
# Transpose to get features by column
columns = list(zip(*features))
normalized_columns = []
for column in columns:
min_val = min(column)
max_val = max(column)
range_val = max_val - min_val
normalized = [(x - min_val) / range_val for x in column]
normalized_columns.append(normalized)
# Transpose back
return list(zip(*normalized_columns))
def train_model(features, labels):
"""Simulate model training."""
print(f"Training model with {len(features)} samples...")
return {"accuracy": 0.85, "model": "LogisticRegression"}
def evaluate_model(model, test_features, test_labels):
"""Evaluate model performance."""
print(f"Model accuracy: {model['accuracy']:.1%}")
return model["accuracy"]
# Complete ML pipeline
def ml_pipeline(filepath="data.csv"):
"""Complete machine learning pipeline."""
# Load data
print("๐ Loading data...")
data = load_data(filepath)
# Extract features and labels
print("๐ง Extracting features...")
features = extract_features(data)
labels = [record["purchased"] for record in data]
# Normalize features
print("๐ Normalizing features...")
normalized_features = normalize_features(features)
# Train model
print("๐ค Training model...")
model = train_model(normalized_features, labels)
# Evaluate
print("๐ Evaluating model...")
accuracy = evaluate_model(model, normalized_features, labels)
return model, accuracy
# Run the pipeline
model, accuracy = ml_pipeline()
print(f"\nโ
Pipeline complete! Final accuracy: {accuracy:.1%}")
Conclusion: Your Function Mastery Journey
Congratulations! You've learned the essentials of Python functions:
โ
Function basics - Creating and calling functions with return values
โ
Parameters - Positional, default, *args, and **kwargs
โ
Lambda functions - Quick one-line functions for simple operations
โ
Map/Filter/Reduce - Functional programming for data processing
โ
Decorators - Enhancing functions with superpowers
โ
Best practices - Writing clean, maintainable, professional code
Next Steps
- Practice daily - Write at least one function every day
- Refactor old code - Turn repetitive code into reusable functions
- Read library code - See how professionals structure functions
- Build projects - Apply functions in real data science projects
- Learn type hints - Explore Python's type annotation system
Quick Reference Cheat Sheet
# Basic function
def function_name(param1, param2):
"""Docstring describing function."""
return param1 + param2
# Default parameters
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Variable arguments
def sum_all(*numbers):
return sum(numbers)
# Keyword arguments
def configure(**settings):
for key, value in settings.items():
print(f"{key}: {value}")
# Lambda function
square = lambda x: x ** 2
# Map, filter, reduce
list(map(lambda x: x*2, [1, 2, 3])) # [2, 4, 6]
list(filter(lambda x: x > 0, [-1, 0, 1, 2])) # [1, 2]
reduce(lambda x, y: x + y, [1, 2, 3, 4]) # 10
# Decorator
@timing_decorator
def my_function():
pass
Functions are the foundation of all Python programming. Master them, and you'll write cleaner, more professional code that your future self (and your team) will thank you for!
Remember: Start simple, practice consistently, and build real projects. You've got this! ๐
If you found this guide helpful and are building cool Python projects, I'd love to hear about them! Share your progress with me on Twitter or connect on LinkedIn. Let's learn together!
Support My Work
If this comprehensive guide helped you master Python functions, understand lambda expressions and decorators, or build better data science projects, I'd really appreciate your support! Creating detailed, beginner-friendly content like this takes significant time and effort. Your support helps me continue sharing knowledge and creating more helpful resources for aspiring developers and data scientists.
โ Buy me a coffee - Every contribution, big or small, means the world to me and keeps me motivated to create more content!
Cover image by Alex Beauchamp on Unsplash