Claude Code for cProfile: Python CPU Performance Profiling — Claude Skills 360 Blog
Blog / AI / Claude Code for cProfile: Python CPU Performance Profiling
AI

Claude Code for cProfile: Python CPU Performance Profiling

Published: August 31, 2028
Read time: 5 min read
By: Claude Skills 360

Python’s cProfile module profiles Python programs by counting function calls and measuring time. import cProfile, pstats. cProfile.run: cProfile.run("main()", "output.prof") — profile a code string; optional second arg saves results to file. Profile class: pr = cProfile.Profile()pr.enable() / pr.disable() / pr.runcall(fn, *args) / pr.create_stats() / pr.dump_stats("file.prof"). pstats.Stats: stats = pstats.Stats(pr) or pstats.Stats("file.prof")stats.sort_stats("cumulative") / stats.print_stats(20) / stats.print_callers("fn_name") / stats.print_callees("fn_name"). Sort keys: "cumtime" (cumulative including callees), "tottime" (own time only), "ncalls", "pcalls" (primitive calls), "name", "file", "module". strip_dirs: stats.strip_dirs() removes full path prefixes. print_stats(n): show top n lines; print_stats("pattern") filter by string. Context manager pattern: with cProfile.Profile() as pr: ... → profile a block. CLI: python -m cProfile -s cumtime -o out.prof script.py. snakeviz out.prof for visual flamegraph (PyPI). Column meanings: ncalls = call count, tottime = time in this function (not callees), cumtime = tottime + callees, percall = per-call avg. Claude Code generates hotspot reports, regression detectors, profiling decorators, and automated performance dashboards.

CLAUDE.md for cProfile

## cProfile Stack
- Stdlib: import cProfile, pstats, io
- Quick:  cProfile.run("fn()", sort="cumulative")
- Save:   pr = cProfile.Profile(); pr.runcall(fn); pr.dump_stats("out.prof")
- Read:   stats = pstats.Stats("out.prof").sort_stats("tottime")
-          stats.print_stats(20)
- Filter: stats.print_stats("mymodule")
- Callers:stats.print_callers("slow_fn")

cProfile Profiling Pipeline

# app/profutil.py — profile, report, compare, decorator, hotspot finder
from __future__ import annotations

import cProfile
import io
import pstats
import time
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Callable, Generator, TypeVar

T = TypeVar("T")


# ─────────────────────────────────────────────────────────────────────────────
# 1. Profiling context managers and helpers
# ─────────────────────────────────────────────────────────────────────────────

@contextmanager
def profile(
    sort: str = "cumulative",
    limit: int = 20,
    filter: str = "",
    print_result: bool = True,
) -> Generator[cProfile.Profile, None, None]:
    """
    Context manager that profiles the block and prints or returns stats.

    Example:
        with profile(sort="tottime", limit=10) as pr:
            heavy_computation()
        # top 10 functions by own time are printed
    """
    pr = cProfile.Profile()
    pr.enable()
    try:
        yield pr
    finally:
        pr.disable()
        if print_result:
            s = io.StringIO()
            stats = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sort)
            if filter:
                stats.print_stats(limit, filter)
            else:
                stats.print_stats(limit)
            print(s.getvalue())


def profile_fn(fn: Callable[..., T], *args: Any, sort: str = "cumulative", limit: int = 20, **kwargs: Any) -> tuple[T, cProfile.Profile]:
    """
    Profile a single function call; return (result, Profile).

    Example:
        result, pr = profile_fn(sorted, list(range(10000, 0, -1)))
        stats = pstats.Stats(pr).sort_stats("tottime")
        stats.print_stats(5)
    """
    pr = cProfile.Profile()
    result = pr.runcall(fn, *args, **kwargs)
    return result, pr


def profile_to_string(pr: cProfile.Profile, sort: str = "cumulative", limit: int = 30, filter: str = "") -> str:
    """
    Format a Profile object as a string (for logging or CI artifacts).

    Example:
        result, pr = profile_fn(main)
        report = profile_to_string(pr, sort="tottime")
        Path("profile.txt").write_text(report)
    """
    s = io.StringIO()
    stats = pstats.Stats(pr, stream=s).strip_dirs().sort_stats(sort)
    if filter:
        stats.print_stats(limit, filter)
    else:
        stats.print_stats(limit)
    return s.getvalue()


def save_profile(pr: cProfile.Profile, path: str) -> None:
    """Save a Profile to a .prof file (readable by pstats, SnakeViz, etc.)."""
    pr.dump_stats(path)


def load_profile(path: str) -> pstats.Stats:
    """Load a .prof file and return a pstats.Stats object."""
    return pstats.Stats(path).strip_dirs()


# ─────────────────────────────────────────────────────────────────────────────
# 2. Profiling decorators
# ─────────────────────────────────────────────────────────────────────────────

def profiled(sort: str = "cumulative", limit: int = 15, filter: str = "") -> Callable:
    """
    Decorator that profiles the wrapped function and prints stats after each call.

    Example:
        @profiled(sort="tottime", limit=10)
        def slow_fn():
            ...
    """
    import functools

    def decorator(fn: Callable) -> Callable:
        @functools.wraps(fn)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            result, pr = profile_fn(fn, *args, **kwargs)
            print(f"\n--- Profile: {fn.__name__} ---")
            print(profile_to_string(pr, sort=sort, limit=limit, filter=filter))
            return result
        return wrapper
    return decorator


def profile_once(fn: Callable[..., T]) -> Callable[..., T]:
    """
    Decorator that profiles the first call and prints stats; all subsequent calls run normally.

    Example:
        @profile_once
        def build_index(data):
            ...
    """
    import functools
    profiled_once = [False]

    @functools.wraps(fn)
    def wrapper(*args: Any, **kwargs: Any) -> T:
        if not profiled_once[0]:
            profiled_once[0] = True
            result, pr = profile_fn(fn, *args, **kwargs)
            print(f"\n--- First-call profile: {fn.__name__} ---")
            print(profile_to_string(pr, limit=10))
            return result
        return fn(*args, **kwargs)
    return wrapper  # type: ignore[return-value]


# ─────────────────────────────────────────────────────────────────────────────
# 3. Hotspot analysis
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class HotspotEntry:
    func:     str
    ncalls:   int
    tottime:  float
    cumtime:  float
    file:     str
    lineno:   int

    @property
    def tottime_µs(self) -> float:
        return self.tottime * 1e6

    @property
    def cumtime_µs(self) -> float:
        return self.cumtime * 1e6

    def __str__(self) -> str:
        return (
            f"{self.func:40s} "
            f"ncalls={self.ncalls:8d}  "
            f"tot={self.tottime_µs:10.1f}µs  "
            f"cum={self.cumtime_µs:10.1f}µs"
        )


def extract_hotspots(pr: cProfile.Profile, n: int = 20, sort: str = "tottime") -> list[HotspotEntry]:
    """
    Extract top n hotspot functions from a Profile as HotspotEntry objects.

    Example:
        _, pr = profile_fn(main)
        for h in extract_hotspots(pr, n=10):
            print(h)
    """
    stats = pstats.Stats(pr).strip_dirs()
    stats.sort_stats(sort)
    # Access internal stats dict: {(file, lineno, func): (ncalls, pcalls, tottime, cumtime, ...)}
    entries = []
    for (file, lineno, func), (ncalls, _, tottime, cumtime, _) in list(stats.stats.items())[:n]:
        entries.append(HotspotEntry(
            func=func, ncalls=ncalls, tottime=tottime, cumtime=cumtime,
            file=file, lineno=lineno,
        ))
    # Sort by chosen criterion
    key_fn = {"tottime": lambda e: -e.tottime, "cumtime": lambda e: -e.cumtime,
               "ncalls": lambda e: -e.ncalls}.get(sort, lambda e: -e.tottime)
    return sorted(entries, key=key_fn)[:n]


# ─────────────────────────────────────────────────────────────────────────────
# 4. Comparison profiling
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class ProfileComparison:
    label_a:   str
    label_b:   str
    hotspots_a: list[HotspotEntry]
    hotspots_b: list[HotspotEntry]

    def report(self) -> str:
        lines = [f"Profile comparison: {self.label_a} vs {self.label_b}"]
        funcs = {h.func for h in self.hotspots_a} | {h.func for h in self.hotspots_b}
        map_a = {h.func: h for h in self.hotspots_a}
        map_b = {h.func: h for h in self.hotspots_b}
        lines.append(f"\n{'Function':40s} {'A totµs':>12} {'B totµs':>12} {'Δ':>10}")
        lines.append("-" * 80)
        for func in sorted(funcs, key=lambda f: -max(
            map_a.get(f, HotspotEntry(f, 0, 0, 0, "", 0)).tottime,
            map_b.get(f, HotspotEntry(f, 0, 0, 0, "", 0)).tottime,
        ))[:20]:
            ta = map_a.get(func, HotspotEntry(func, 0, 0, 0, "", 0)).tottime_µs
            tb = map_b.get(func, HotspotEntry(func, 0, 0, 0, "", 0)).tottime_µs
            delta = tb - ta
            lines.append(f"{func:40s} {ta:12.1f} {tb:12.1f} {delta:+10.1f}")
        return "\n".join(lines)


def compare_profiles(
    fn_a: Callable, label_a: str,
    fn_b: Callable, label_b: str,
    *args: Any, **kwargs: Any,
) -> ProfileComparison:
    """
    Profile two functions with identical arguments and compare hotspots.

    Example:
        cmp = compare_profiles(old_sort, "old", new_sort, "new", data=big_list)
        print(cmp.report())
    """
    _, pr_a = profile_fn(fn_a, *args, **kwargs)
    _, pr_b = profile_fn(fn_b, *args, **kwargs)
    return ProfileComparison(
        label_a=label_a,
        label_b=label_b,
        hotspots_a=extract_hotspots(pr_a),
        hotspots_b=extract_hotspots(pr_b),
    )


# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    print("=== cProfile demo ===")

    def slow_a(n):
        """Lots of small calls."""
        total = 0
        for i in range(n):
            total += sum(range(i))
        return total

    def slow_b(n):
        """Fewer larger calls."""
        return sum(sum(range(i)) for i in range(n))

    print("\n--- profile context manager ---")
    with profile(sort="tottime", limit=8) as pr:
        slow_a(200)

    print("\n--- profile_to_string ---")
    _, pr = profile_fn(slow_a, 300)
    report = profile_to_string(pr, sort="tottime", limit=5)
    print(report)

    print("\n--- extract_hotspots ---")
    _, pr = profile_fn(slow_b, 300)
    for h in extract_hotspots(pr, n=5):
        print(f"  {h}")

    print("\n--- compare_profiles ---")
    cmp = compare_profiles(slow_a, "slow_a", slow_b, "slow_b", 300)
    print(cmp.report())

    print("\n=== done ===")

For the line_profiler alternative — line_profiler (PyPI via kernprof) instruments individual lines within functions, showing exactly which line costs the most time; cProfile instruments at the function granularity — use line_profiler with @profile decorator when cProfile has already told you which function is slow and you need to pinpoint the hot line inside it; use cProfile as the first step to identify which functions are worth line-level profiling. For the py-spy / pyinstrument alternative — py-spy (PyPI) is a sampling profiler that can attach to a running Python process without modifying the source and produces flamegraph SVGs with near-zero overhead; pyinstrument (PyPI) wraps time.perf_counter sampling and renders call trees with live HTML output; they are ideal for profiling long-running servers and web requests without code modification — use py-spy for low-overhead production sampling; use pyinstrument for request-level profiling in web frameworks; use cProfile for deterministic (every-call-counted) analysis of offline batch jobs and scripts. The Claude Skills 360 bundle includes cProfile skill sets covering profile()/profile_fn()/profile_to_string()/save_profile()/load_profile() core helpers, profiled()/profile_once() decorators, HotspotEntry dataclass with extract_hotspots(), ProfileComparison/compare_profiles() for before-and-after comparison, and full demo comparing two implementations. Start with the free tier to try performance profiling patterns and cProfile pipeline code generation.

Keep Reading

AI

Claude Code for email.contentmanager: Python Email Content Accessors

Read and write EmailMessage body content with Python's email.contentmanager module and Claude Code — email contentmanager ContentManager for the class that maps content types to get and set handler functions allowing EmailMessage to support get_content and set_content with type-specific behaviour, email contentmanager raw_data_manager for the ContentManager instance that handles raw bytes and str payloads without any conversion, email contentmanager content_manager for the standard ContentManager instance used by email.policy.default that intelligently handles text plain text html multipart and binary content types, email contentmanager get_content_text for the handler that returns the decoded text payload of a text-star message part as a str, email contentmanager get_content_binary for the handler that returns the raw decoded bytes payload of a non-text message part, email contentmanager get_data_manager for the get-handler lookup used by EmailMessage get_content to find the right reader function for the content type, email contentmanager set_content text for the handler that creates and sets a text part correctly choosing charset and transfer encoding, email contentmanager set_content bytes for the handler that creates and sets a binary part with base64 encoding and optional filename Content-Disposition, email contentmanager EmailMessage get_content for the method that reads the message body using the registered content manager handlers, email contentmanager EmailMessage set_content for the method that sets the message body and MIME headers in one call, email contentmanager EmailMessage make_alternative make_mixed make_related for the methods that convert a simple message into a multipart container, email contentmanager EmailMessage add_attachment for the method that attaches a file or bytes to a multipart message, and email contentmanager integration with email.message and email.policy and email.mime and io for building high-level email readers attachment extractors text body accessors HTML readers and policy-aware MIME construction pipelines.

5 min read Feb 12, 2029
AI

Claude Code for email.charset: Python Email Charset Encoding

Control header and body encoding for international email with Python's email.charset module and Claude Code — email charset Charset for the class that wraps a character set name with the encoding rules for header encoding and body encoding describing how to encode text for that charset in email messages, email charset Charset header_encoding for the attribute specifying whether headers using this charset should use QP quoted-printable encoding BASE64 encoding or no encoding, email charset Charset body_encoding for the attribute specifying the Content-Transfer-Encoding to use for message bodies in this charset such as QP or BASE64, email charset Charset output_codec for the attribute giving the Python codec name used to encode the string to bytes for the wire format, email charset Charset input_codec for the attribute giving the Python codec name used to decode incoming bytes to str, email charset Charset get_output_charset for returning the output charset name, email charset Charset header_encode for encoding a header string using the charset's header_encoding method, email charset Charset body_encode for encoding body content using the charset's body_encoding, email charset Charset convert for converting a string from the input_codec to the output_codec, email charset add_charset for registering a new charset with custom encoding rules in the global charset registry, email charset add_alias for adding an alias name that maps to an existing registered charset, email charset add_codec for registering a codec name mapping for use by the charset machinery, and email charset integration with email.message and email.mime and email.policy and email.encoders for building international email senders non-ASCII header encoders Content-Transfer-Encoding selectors charset-aware message constructors and MIME encoding pipelines.

5 min read Feb 11, 2029
AI

Claude Code for email.utils: Python Email Address and Header Utilities

Parse and format RFC 2822 email addresses and dates with Python's email.utils module and Claude Code — email utils parseaddr for splitting a display-name plus angle-bracket address string into a realname and email address tuple, email utils formataddr for combining a realname and address string into a properly quoted RFC 2822 address with angle brackets, email utils getaddresses for parsing a list of raw address header strings each potentially containing multiple comma-separated addresses into a list of realname address tuples, email utils parsedate for parsing an RFC 2822 date string into a nine-tuple compatible with time.mktime, email utils parsedate_tz for parsing an RFC 2822 date string into a ten-tuple that includes the UTC offset timezone in seconds, email utils parsedate_to_datetime for parsing an RFC 2822 date string into an aware datetime object with timezone, email utils formatdate for formatting a POSIX timestamp or the current time as an RFC 2822 date string with optional usegmt and localtime flags, email utils format_datetime for formatting a datetime object as an RFC 2822 date string, email utils make_msgid for generating a globally unique Message-ID string with optional idstring and domain components, email utils decode_rfc2231 for decoding an RFC 2231 encoded parameter value into a tuple of charset language and value, email utils encode_rfc2231 for encoding a string as an RFC 2231 encoded parameter value, email utils collapse_rfc2231_value for collapsing a decoded RFC 2231 tuple to a Unicode string, and email utils integration with email.message and email.headerregistry and datetime and time for building address parsers date formatters message-id generators header extractors and RFC-compliant email construction utilities.

5 min read Feb 10, 2029

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free