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

๐Ÿ“… Published: April 2, 2025 โœ๏ธ Updated: May 20, 2025 By Ojaswi Athghara
#python #functions #lambda #decorators #map-filter

Python Functions Tutorial: From Basics to Lambda Functions for Beginners

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:

  1. def - Python keyword that says "I'm defining a function"
  2. greet - The function name (use descriptive names!)
  3. () - Parentheses hold parameters (empty here)
  4. : - Colon starts the function body
  5. Indented code - The function's instructions
  6. 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:

  1. Regular parameters
  2. Default parameters
  3. *args
  4. **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_decorator is 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

  1. Practice daily - Write at least one function every day
  2. Refactor old code - Turn repetitive code into reusable functions
  3. Read library code - See how professionals structure functions
  4. Build projects - Apply functions in real data science projects
  5. 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

Related Blogs

Ojaswi Athghara

SDE, 4+ Years

ยฉ ojaswiat.com 2025-2027