Basic Ingredients of a Good Function in Python – Data Science Perspective 2026

Data Science Tool Box Mar 18, 2026

Basic Ingredients of a Good Function in Python – Data Science Perspective 2026

Writing good functions is a fundamental skill for any data scientist. A well-crafted function should be clear, reusable, maintainable, and easy to test. In 2026, there are several essential "ingredients" that separate average functions from excellent ones.

TL;DR — The 7 Essential Ingredients of a Good Function

  • Clear and descriptive name
  • Single Responsibility (does one thing well)
  • Type hints for parameters and return value
  • Comprehensive docstring
  • Sensible default values
  • Proper error handling
  • Clean and readable implementation

1. Complete Example of a Good Data Science Function

from typing import List, Dict, Any, Optional
import pandas as pd

def summarize_customer_behavior(
    transactions: List[Dict[str, Any]],
    min_transactions: int = 3,
    include_inactive: bool = False
) -> Dict[str, Any]:
    """
    Summarize customer behavior from transaction data.
    
    This function calculates key customer metrics including total spend,
    average order value, and activity status.
    
    Args:
        transactions: List of transaction dictionaries containing 
                      'customer_id', 'amount', 'date', etc.
        min_transactions: Minimum number of transactions to consider 
                          a customer active (default: 3).
        include_inactive: Whether to include customers below the 
                          min_transactions threshold.
    
    Returns:
        Dictionary containing:
            - total_customers
            - active_customers
            - total_revenue
            - avg_order_value
            - customer_metrics (DataFrame)
    
    Example:
        >>> result = summarize_customer_behavior(transactions)
        >>> print(result['avg_order_value'])
    """
    df = pd.DataFrame(transactions)
    
    # Basic cleaning
    df = df.dropna(subset=["customer_id", "amount"])
    
    # Calculate customer-level metrics
    customer_metrics = df.groupby("customer_id").agg({
        "amount": ["sum", "mean", "count"],
        "date": ["min", "max"]
    }).reset_index()
    
    customer_metrics.columns = ["customer_id", "total_spend", "avg_order_value", 
                                "transaction_count", "first_purchase", "last_purchase"]
    
    # Filter active customers
    if not include_inactive:
        customer_metrics = customer_metrics[
            customer_metrics["transaction_count"] >= min_transactions
        ]
    
    return {
        "total_customers": len(customer_metrics),
        "active_customers": len(customer_metrics),
        "total_revenue": customer_metrics["total_spend"].sum(),
        "avg_order_value": customer_metrics["avg_order_value"].mean(),
        "customer_metrics": customer_metrics
    }

2. Best Practices for Function Ingredients in 2026

  • Name: Use clear, verb-based names (e.g., calculate_revenue, clean_customer_data)
  • Single Responsibility: Each function should do one logical task
  • Type Hints: Always include type hints for parameters and return values
  • Docstring: Write clear Google-style docstrings with Args, Returns, and Example sections
  • Defaults: Provide sensible default values for optional parameters
  • Error Handling: Handle common edge cases gracefully
  • Readability: Keep functions short and focused (ideally under 30–40 lines)

The Ingredients Together

A good function has a clear name that says what it does, a small set of well-named parameters, one responsibility, a docstring, and a predictable return value. It avoids hidden side effects such as changing global state or the caller's data.

These ingredients make functions easy to test. If you cannot write a short test for a function, it is usually doing too much and should be split.

Worked Example: Refactoring a Messy Function

A function that loads, cleans and prints at once is split into focused pieces.

import pandas as pd

def to_numeric_column(df: pd.DataFrame, column: str) -> pd.DataFrame:
    """Return a copy with column converted to numbers."""
    out = df.copy()
    out[column] = pd.to_numeric(out[column], errors="coerce")
    return out

def total_by(df: pd.DataFrame, key: str, value: str) -> pd.Series:
    """Sum value per key."""
    return df.groupby(key)[value].sum()

raw = pd.DataFrame({"region": ["N", "S", "N"], "sales": ["10", "5", "x"]})
clean = to_numeric_column(raw, "sales")
totals = total_by(clean, "region", "sales")
print(totals)
assert totals["S"] == 5

Each function now does one thing and can be tested alone. The invalid value x becomes NaN and is skipped by the sum, so region N totals 10.

Practical Tips

Defaults are another ingredient: choose values that suit the most common case, so typical calls stay short. Finally, validate inputs early and raise clear errors, which saves time when someone passes the wrong column name.

Common Mistakes

  • Names like process or do_stuff that hide the purpose.
  • Functions that read files, clean data and plot all at once.
  • Hidden changes to inputs or globals.
  • No docstring or tests.

Quick Summary

  • Clear name, few parameters, one job.
  • A docstring and a predictable return.
  • No hidden side effects.
  • Easy to test means well designed.

Conclusion

A good function is more than just working code — it is clear, well-documented, typed, and follows the single responsibility principle. In 2026, data scientists who consistently apply these ingredients write code that is easier to maintain, test, and collaborate on. Strong function design is one of the biggest differentiators between junior and senior data scientists.

Next steps:

  • Review your current data science functions and evaluate them against these seven ingredients
  • Refactor at least one function to improve its name, docstring, type hints, and structure

Related Tutorials

Share:
Last updated: March 2026
Built-in Functions in Python 2026 str() in Python 2026: String Conversion + Modern Formatting & Best Practices memoryview with JAX in Python 2026: Zero-Copy NumPy → JAX Array Interop + Efficient ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + GPU Pinning & ML Examples memoryview with TensorFlow in Python 2026: Zero-Copy NumPy → Tensor Interop + ML Examples memoryview with NumPy & PyTorch in Python 2026: Zero-Copy Views, Efficient Slicing & ML Interop Examples memoryview with NumPy in Python 2026: Zero-Copy Views, Efficient Slicing & Real ML Examples memoryview() in Python 2026: Zero-Copy Magic for Large Binary Data + Real Examples import() in Python 2026: How It Works, Security Risks & Modern Alternatives (importlib) zip() in Python 2026: How to Use It, strict=True Behavior & Real-World Examples vars() in Python 2026: Accessing Object Namespace + Modern Introspection Patterns type() in Python 2026: Dynamic Type Inspection & Object Creation + Modern Patterns tuple() in Python 2026: Immutable Sequences + Modern Patterns & Best Practices super() in Python 2026: Method Resolution & Modern Inheritance Patterns sum() in Python 2026: Summing Iterables + Modern Numeric Patterns & Best Practices staticmethod() in Python 2026: Static Methods, Utility Functions & Modern Best Practices sorted() in Python 2026: Sorting Iterables + Modern Patterns & Best Practices slice() in Python 2026: Creating Slice Objects + Modern Patterns & Best Practices setattr() in Python 2026: Dynamic Attribute Setting + Modern Patterns & Safety set() in Python 2026: Mutable Sets Creation + Modern Patterns & Best Practices