Claude Code for tracemalloc: Python Memory Allocation Tracing — Claude Skills 360 Blog
Blog / AI / Claude Code for tracemalloc: Python Memory Allocation Tracing
AI

Claude Code for tracemalloc: Python Memory Allocation Tracing

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

Python’s tracemalloc module traces memory block allocations, enabling identification of memory leaks and hotspots. import tracemalloc. start: tracemalloc.start(25) — begin tracing; 25 = frames in call stack per allocation. stop: tracemalloc.stop(). take_snapshot: snap = tracemalloc.take_snapshot()Snapshot object. get_traced_memory: cur, peak = tracemalloc.get_traced_memory() → bytes. get_tracemalloc_memory: overhead of tracemalloc itself in bytes. clear_traces: tracemalloc.clear_traces() — reset counts. tracemalloc.is_tracing(). Snapshot.statistics: stats = snap.statistics("lineno") — group by "lineno", "filename", "traceback"; returns list of Statistic sorted by size descending. Snapshot.compare_to: diffs = snap2.compare_to(snap1, "lineno") → list of StatisticDiff (size_diff, count_diff). filter_traces: snap.filter_traces([tracemalloc.Filter(inclusive, pattern)]) — include/exclude tracebacks. Statistic: .traceback, .size, .count. StatisticDiff: .size_diff, .count_diff, .size, .count. Traceback: .format(limit=10). tracemalloc.get_object_traceback(obj) — traceback for a specific object. Filter: tracemalloc.Filter(True, "mymodule*") — inclusive filter. Claude Code generates leak detectors, allocation reporters, before/after diff tools, and memory budget guards.

CLAUDE.md for tracemalloc

## tracemalloc Stack
- Stdlib: import tracemalloc
- Start:  tracemalloc.start(25)   # 25 = frames depth
- Snap:   snap = tracemalloc.take_snapshot()
- Top:    for s in snap.statistics("lineno")[:10]: print(s)
- Diff:   diffs = snap2.compare_to(snap1, "lineno")
- Peak:   cur, peak = tracemalloc.get_traced_memory()
- Stop:   tracemalloc.stop()

tracemalloc Memory Tracing Pipeline

# app/tracemallocutil.py — trace, snapshot, top, diff, leak monitor, budget
from __future__ import annotations

import gc
import linecache
import tracemalloc
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Any, Callable, Generator


# ─────────────────────────────────────────────────────────────────────────────
# 1. Tracing context manager
# ─────────────────────────────────────────────────────────────────────────────

@contextmanager
def trace_memory(
    nframe: int = 25,
    gc_collect: bool = True,
) -> Generator[None, None, None]:
    """
    Context manager that enables tracemalloc for the enclosed block.

    nframe: number of call stack frames captured per allocation.
    gc_collect: force GC before and after to reduce noise.

    Example:
        with trace_memory():
            my_function()
        snap = tracemalloc.take_snapshot()
        print_top(snap, limit=10)
    """
    if gc_collect:
        gc.collect()
    tracemalloc.start(nframe)
    try:
        yield
    finally:
        if gc_collect:
            gc.collect()
        tracemalloc.stop()


# ─────────────────────────────────────────────────────────────────────────────
# 2. Snapshot helpers
# ─────────────────────────────────────────────────────────────────────────────

def take_snapshot(
    exclude_stdlib: bool = True,
    exclude_tracemalloc: bool = True,
) -> tracemalloc.Snapshot:
    """
    Take a filtered memory snapshot.

    Example:
        snap = take_snapshot()
        print_top(snap, limit=10)
    """
    import sys
    snap = tracemalloc.take_snapshot()
    filters: list[tracemalloc.Filter] = []
    if exclude_tracemalloc:
        filters.append(tracemalloc.Filter(False, tracemalloc.__file__))
    if exclude_stdlib:
        import sysconfig
        stdlib_path = sysconfig.get_path("stdlib")
        if stdlib_path:
            filters.append(tracemalloc.Filter(False, stdlib_path + "/*"))
    if filters:
        snap = snap.filter_traces(filters)
    return snap


@dataclass
class MemoryStat:
    file:    str
    line:    int
    size:    int       # bytes
    count:   int
    code:    str       # source line text

    def __str__(self) -> str:
        size_kb = self.size / 1024
        return (f"{size_kb:8.1f} KiB  "
                f"{self.count:6d} objects  "
                f"{self.file}:{self.line}  {self.code.strip()!r}")


def top_allocations(
    snap: tracemalloc.Snapshot,
    limit: int = 20,
    key: str = "lineno",
) -> list[MemoryStat]:
    """
    Return the top allocation sites from a snapshot.
    key: "lineno" (default), "filename", "traceback".

    Example:
        with trace_memory():
            workload()
        snap = take_snapshot()
        for s in top_allocations(snap, limit=10):
            print(s)
    """
    stats = snap.statistics(key)
    results: list[MemoryStat] = []
    for stat in stats[:limit]:
        tb = stat.traceback
        frame = tb[0] if tb else None
        file = frame.filename if frame else "?"
        line = frame.lineno if frame else 0
        code = linecache.getline(file, line) if frame else ""
        results.append(MemoryStat(
            file=file, line=line, size=stat.size, count=stat.count, code=code,
        ))
    return results


def print_top(snap: tracemalloc.Snapshot, limit: int = 10) -> None:
    """Print top allocation sites to stdout."""
    for s in top_allocations(snap, limit=limit):
        print(f"  {s}")


# ─────────────────────────────────────────────────────────────────────────────
# 3. Diff / leak detection
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class AllocDiff:
    file:      str
    line:      int
    size_diff: int     # bytes (positive = grew)
    count_diff: int
    size_now:  int
    code:      str

    @property
    def is_growth(self) -> bool:
        return self.size_diff > 0

    def __str__(self) -> str:
        sign = "+" if self.size_diff >= 0 else ""
        return (f"{sign}{self.size_diff / 1024:+.1f} KiB  "
                f"{sign}{self.count_diff:+d} objects  "
                f"{self.file}:{self.line}  {self.code.strip()!r}")


def diff_snapshots(
    before: tracemalloc.Snapshot,
    after: tracemalloc.Snapshot,
    limit: int = 20,
    key: str = "lineno",
    only_growth: bool = True,
) -> list[AllocDiff]:
    """
    Diff two snapshots and return sorted allocation deltas.

    Example:
        snap1 = take_snapshot()
        do_work()
        snap2 = take_snapshot()
        for d in diff_snapshots(snap1, snap2):
            print(d)
    """
    stats = after.compare_to(before, key)
    if only_growth:
        stats = [s for s in stats if s.size_diff > 0]
    results: list[AllocDiff] = []
    for stat in stats[:limit]:
        tb = stat.traceback
        frame = tb[0] if tb else None
        file = frame.filename if frame else "?"
        line = frame.lineno if frame else 0
        code = linecache.getline(file, line) if frame else ""
        results.append(AllocDiff(
            file=file, line=line,
            size_diff=stat.size_diff, count_diff=stat.count_diff,
            size_now=stat.size, code=code,
        ))
    results.sort(key=lambda d: -d.size_diff)
    return results


# ─────────────────────────────────────────────────────────────────────────────
# 4. Memory budget / guard
# ─────────────────────────────────────────────────────────────────────────────

class MemoryBudget:
    """
    Context manager that asserts memory growth stays within a budget.
    Raises MemoryError if the traced memory increases by more than budget_bytes.

    Example:
        with MemoryBudget(1024 * 1024):   # 1 MiB budget
            small_operation()
        # Raises MemoryError if > 1 MiB was allocated net
    """

    def __init__(self, budget_bytes: int, nframe: int = 10) -> None:
        self.budget_bytes = budget_bytes
        self.nframe = nframe
        self._was_tracing = False
        self._start_snap: tracemalloc.Snapshot | None = None

    def __enter__(self) -> "MemoryBudget":
        self._was_tracing = tracemalloc.is_tracing()
        if not self._was_tracing:
            tracemalloc.start(self.nframe)
        gc.collect()
        self._start_snap = tracemalloc.take_snapshot()
        return self

    def __exit__(self, *args) -> None:
        gc.collect()
        end_snap = tracemalloc.take_snapshot()
        if not self._was_tracing:
            tracemalloc.stop()
        if self._start_snap is not None:
            diffs = end_snap.compare_to(self._start_snap, "lineno")
            net = sum(d.size_diff for d in diffs)
            if net > self.budget_bytes:
                top = sorted(diffs, key=lambda d: -d.size_diff)[:3]
                details = "; ".join(
                    f"{d.traceback[0].filename}:{d.traceback[0].lineno} +{d.size_diff/1024:.1f}KiB"
                    for d in top if d.size_diff > 0 and d.traceback
                )
                raise MemoryError(
                    f"Memory budget exceeded: {net/1024:.1f} KiB allocated "
                    f"(budget {self.budget_bytes/1024:.1f} KiB). Top: {details}"
                )


# ─────────────────────────────────────────────────────────────────────────────
# 5. Repeated-call leak monitor
# ─────────────────────────────────────────────────────────────────────────────

@dataclass
class LeakReport:
    iterations:   int
    total_growth: int    # bytes
    per_iter:     float  # bytes / iter
    top_sites:    list[AllocDiff] = field(default_factory=list)

    def __str__(self) -> str:
        return (f"iterations={self.iterations}  "
                f"growth={self.total_growth / 1024:.1f} KiB total  "
                f"{self.per_iter / 1024:.2f} KiB/iter")


def detect_leak(
    fn: Callable,
    iterations: int = 10,
    warmup: int = 2,
    nframe: int = 25,
) -> LeakReport:
    """
    Call fn repeatedly and check if memory grows with each iteration.
    Warmup runs are discarded before the baseline snapshot.

    Example:
        def maybe_leaks():
            global cache
            cache = {i: i*i for i in range(1000)}
        report = detect_leak(maybe_leaks, iterations=5)
        print(report)
        for site in report.top_sites:
            print(f"  {site}")
    """
    if not tracemalloc.is_tracing():
        tracemalloc.start(nframe)
        started = True
    else:
        started = False

    try:
        for _ in range(warmup):
            fn()
        gc.collect()
        snap_before = tracemalloc.take_snapshot()

        for _ in range(iterations):
            fn()
        gc.collect()
        snap_after = tracemalloc.take_snapshot()

    finally:
        if started:
            tracemalloc.stop()

    diffs = diff_snapshots(snap_before, snap_after, limit=10, only_growth=True)
    total = sum(d.size_diff for d in diffs)
    return LeakReport(
        iterations=iterations,
        total_growth=total,
        per_iter=total / iterations if iterations else 0,
        top_sites=diffs[:5],
    )


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

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

    # ── basic trace + top ──────────────────────────────────────────────────────
    print("\n--- top allocations ---")
    tracemalloc.start(25)

    # Allocate some data
    big_list = [bytearray(1024) for _ in range(100)]
    nested = {str(i): list(range(100)) for i in range(50)}

    snap = take_snapshot(exclude_stdlib=True)
    tracemalloc.stop()

    print(f"  snapshot: {len(snap.statistics('lineno'))} allocation sites")
    for s in top_allocations(snap, limit=3):
        print(f"  {s}")

    # ── diff_snapshots ──────────────────────────────────────────────────────────
    print("\n--- diff_snapshots ---")
    tracemalloc.start(25)
    gc.collect()
    snap1 = take_snapshot()

    held = {i: bytearray(512) for i in range(200)}  # 200 * 512 bytes

    gc.collect()
    snap2 = take_snapshot()
    tracemalloc.stop()

    diffs = diff_snapshots(snap1, snap2, limit=5)
    for d in diffs:
        print(f"  {d}")

    # ── get_traced_memory ───────────────────────────────────────────────────────
    print("\n--- get_traced_memory ---")
    tracemalloc.start(10)
    stuff = [list(range(1000)) for _ in range(20)]
    cur, peak = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    print(f"  current: {cur/1024:.1f} KiB  peak: {peak/1024:.1f} KiB")

    # ── MemoryBudget ────────────────────────────────────────────────────────────
    print("\n--- MemoryBudget ---")
    try:
        with MemoryBudget(10 * 1024 * 1024):   # 10 MiB — should pass
            tiny = list(range(100))
        print("  10 MiB budget: passed")
    except MemoryError as e:
        print(f"  unexpected: {e}")

    try:
        with MemoryBudget(1024):   # 1 KiB — should fail
            _big = bytearray(1024 * 1024)
        print("  1 KiB budget: unexpectedly passed")
    except MemoryError as e:
        print(f"  1 KiB budget exceeded: {str(e)[:80]}")

    # ── detect_leak ─────────────────────────────────────────────────────────────
    print("\n--- detect_leak ---")
    _accumulator: list = []
    def _leak_fn() -> None:
        _accumulator.extend(range(100))

    report = detect_leak(_leak_fn, iterations=5, warmup=1)
    print(f"  {report}")
    for site in report.top_sites[:2]:
        print(f"  {site}")

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

For the memray alternative — memray (PyPI) is a deterministic memory profiler that records allocations at the C level, captures native stack frames, exports flame graphs, and provides both CLI and programmatic APIs — use memray for deep production memory profiling with minimal code changes and rich visualization; use tracemalloc when you want zero third-party dependencies, need a pure Python context-manager-based approach, or are running in environments where installing native extensions is not possible. For the gc alternative — Python’s gc module exposes the garbage collector: gc.get_objects() lists all tracked objects; gc.get_referrers(obj) shows what holds a reference; gc.collect() forces a GC cycle — use gc to debug reference cycles and understand why specific objects are being retained; combine tracemalloc (to identify which allocation sites are growing) with gc.get_referrers() (to find what holds references to the leaked objects) for a complete leak investigation. The Claude Skills 360 bundle includes tracemalloc skill sets covering trace_memory() context manager, take_snapshot() with stdlib filtering, MemoryStat with top_allocations()/print_top(), AllocDiff with diff_snapshots(), MemoryBudget context manager with budget enforcement, and LeakReport with detect_leak() repeated-call leak detector. Start with the free tier to try memory leak detection patterns and tracemalloc 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