Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python  Module 17 – Performance, profiling and debugging in depth

Finding memory problems with tracemalloc

Find what makes a Python program use more and more memory: take tracemalloc snapshots, compare them line by line, and tell shallow sizes from real ones.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Two snapshots show which line holds the new memory · Memory stays alive while something refers to it · sys.getsizeof() measures one object · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Measure allocations with tracemalloc snapshots, statistics and peaks
  • Find the line and the reference behind memory that keeps growing
  • Explain why sys.getsizeof() undercounts containers, with a measurement
  • Save memory with bounded caches, generators, slots and arrays

Before you start

After 5,000 requests from new users, a small service holds 3,621 KiB more than before, and nothing in it will ever free that memory. The standard library’s tracemalloc module finds such growth: it records every block of memory that Python allocates, with the file and line that asked for it. Start it with tracemalloc.start(), take a snapshot with tracemalloc.take_snapshot(), and snapshot.statistics("lineno") lists the lines that hold the most memory.

A second snapshot taken later, compared with compare_to(), shows what grew in between, and get_traced_memory() returns the current and the peak size. In Python, memory that seems to leak is nearly always memory that something still refers to: a cache that never forgets, a module-level list that only grows, or a reference cycle: Objects that refer to each other, or one object that refers to itself, so that their reference counts never reach 0 on their own. CPython's garbage collector finds such groups when nothing else can reach them, and frees them. More in “__slots__, weak references and object lifetime” waiting for the garbage collector: The part of CPython that looks for reference cycles that nothing can reach any more and frees them. It runs by itself from time to time, and gc.collect() runs it at once. More in “__slots__, weak references and object lifetime”. Below, two snapshots find that service’s growing cache, and a measurement shows why sys.getsizeof() alone gives the wrong answer.

Two snapshots show which line holds the new memory

load_profile() keeps every profile it reads in a dictionary at module level, so that the next request for the same user is fast. The program serves 1,000 requests to warm up, takes a snapshot, serves 5,000 more from new users and takes another. filter_traces() leaves out tracemalloc’s own allocations, and compare_to(before, "lineno") sorts the differences by size:

Where the memory of 5,000 requests went Python · cache_growth.py
import linecache
import tracemalloc

_profiles = {}  # module level: lives as long as the program


def load_profile(user_id):
    """Pretend to read a user's profile from a database, and keep it for next time."""
    if user_id not in _profiles:
        _profiles[user_id] = {"id": user_id, "name": f"user-{user_id}", "history": list(range(50))}
    return _profiles[user_id]


def handle_request(user_id):
    return len(load_profile(user_id)["history"])


tracemalloc.start()
# Warm up first: the first requests may allocate things that stay, such as caches inside Python itself.
for user_id in range(1_000):
    handle_request(user_id)
before = tracemalloc.take_snapshot()
# 5,000 more requests, each from a new user.
for user_id in range(1_000, 6_000):
    handle_request(user_id)
after = tracemalloc.take_snapshot()

ignore = [tracemalloc.Filter(False, tracemalloc.__file__)]  # leave out tracemalloc's own allocations
growth = after.filter_traces(ignore).compare_to(before.filter_traces(ignore), "lineno")
print(f"traced memory now: {tracemalloc.get_traced_memory()[0] / 1024:,.0f} KiB")
print("largest growth between the two snapshots:")
for stat in growth[:2]:
    frame = stat.traceback[0]
    source = linecache.getline(frame.filename, frame.lineno).strip()
    print(f"  line {frame.lineno}: +{stat.size_diff / 1024:,.0f} KiB in {stat.count_diff:+,} blocks   {source}")

Output

traced memory now: 4,521 KiB
largest growth between the two snapshots:
  line 10: +3,621 KiB in +25,000 blocks   _profiles[user_id] = {"id": user_id, "name": f"user-{user_id}", "history": list(range(50))}
  line 24: +156 KiB in +5,000 blocks   for user_id in range(1_000, 6_000):

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 cache_growth.py

Version note

The browser runner is a 32-bit build, where a reference takes 4 bytes instead of 8, so every size comes out about half as large there. The lines and the numbers of blocks are the same.

In your browser, Pyodide 314.0.7 (CPython 3.14.2) prints:

traced memory now: 2,389 KiB
largest growth between the two snapshots:
  line 10: +1,927 KiB in +25,000 blocks   _profiles[user_id] = {"id": user_id, "name": f"user-{user_id}", "history": list(range(50))}
  line 24: +78 KiB in +5,000 blocks   for user_id in range(1_000, 6_000):

Step through the measurement, one stage at a time:

Interactive · stage by stage How two snapshots found the line that keeps the memory Step through cache_growth.py as it ran on 64-bit CPython. Each step is one stage of the measurement, with what it found.
How two snapshots found the line that keeps the memory, step 8 of 8. Showing tracemalloc.start(), 1,000 requests, take_snapshot(), 5,000 new users, take_snapshot() (4,521 KiB traced), compare_to(before) (line 24: +156 KiB). In focus: compare_to(before).tracemalloc.start()every block traced1,000 requeststhe warm-uptake_snapshot()before5,000 new usersthe work under testtake_snapshot()4,521 KiB tracedcompare_to(before)line 24: +156 KiB

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Start tracing. tracemalloc.start() makes Python record every block of memory it allocates from now on, with the file and the line that asked for it.
  2. Warm up. 1,000 requests run first. Memory that a program allocates once and keeps on purpose, such as caches inside Python itself, is allocated now, before the first snapshot.
  3. Snapshot before. The first snapshot copies the traces of every block that is alive at this moment. Memory that was allocated and freed again is not in it.
  4. The work. 5,000 more requests come from new users, and load_profile() keeps each new profile in the module-level dict _profiles.
  5. Snapshot after. The second snapshot copies the traces again. The traced memory is now 4,521 KiB.
  6. Compare. compare_to() subtracts the first snapshot from the second, line by line, after filter_traces() has left out the blocks of tracemalloc itself.
  7. The culprit. Line 10, the line that stores a profile in _profiles, holds 3,621 KiB more than before, in 25,000 more blocks, five for each new profile. Nothing has freed them.
  8. The keys. Line 24 holds 156 KiB more, in 5,000 blocks, one int object for each new user id, kept alive as keys of the same dict.

The first line of the difference names the culprit: the line that stores a profile in _profiles holds 3,621 KiB more than before, in 25,000 more blocks, five per new profile: the dictionary and its table of entries, the name string, and the history list with the array of its items. The second line is the loop itself: each user id is a new int object, kept alive because the dictionary keeps it as a key. A snapshot holds only memory that is alive when it is taken, so growth between two snapshots is memory that nothing has freed. The next question is what keeps it: here, the module-level name _profiles.

Checkpoint

Check: Comparing two snapshots

  1. Question 1 of 2 What does a tracemalloc snapshot contain?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The traces of every block that is alive when it is taken: its size and the line that allocated it

    A snapshot is a copy of the traces of the memory blocks alive at that moment, each with its size and where it was allocated. Two snapshots compared show what was allocated in between and not freed.

  2. Question 2 of 2 Between the two snapshots, 5,000 new profiles added 25,000 blocks on line 10. How many blocks does one profile take?

    Type a number.

    A hint

    Divide the blocks by the profiles.

    Show the answer: Question 2 of 2

    Answer: 5 blocks

    25,000 ÷ 5,000 = 5 blocks per profile: the dictionary and its table of entries, the name string, and the history list with the array of its items.

Memory stays alive while something refers to it

CPython frees an object as soon as nothing refers to it any more, when its reference count: The number of strong references to an object, which CPython keeps in the object itself. When it falls to 0, CPython frees the object at once. More in “__slots__, weak references and object lifetime” falls to zero, so memory stays in use for one of two reasons: something reachable still refers to it, or it sits in a reference cycle that the garbage collector has not freed yet. The lesson on object lifetime showed both; this is how they look in a running program:

Diagram: a module-level dict keeps every profile alive; unreferenced objects are freed at once; a cycle waits for the garbage collector.Kept as long asthe program runsFreed at onceFreed only when thegarbage collector runsmodule-level name_profilesdict: one entry per user,never emptiedevery profile ever loadeda list a function buildsand never storeswhen the call returns:no references left, soreference counting frees itabrefers torefers torefers torefers to

What keeps memory alive in a Python program

Text description of the diagram

1. The module-level name _profiles refers to a dict, and the dict refers to every profile ever loaded. Everything reachable from a module-level name lives as long as the program, so this dict grows with every new user. 2. A list that a function builds and never stores anywhere is referred to only by a local variable. When the call returns, the local name goes, nothing refers to the list any more, and reference counting frees it at once. 3. Two objects that refer to each other form a reference cycle. Their reference counts never reach zero, so they stay in memory after the program has dropped them, until the garbage collector finds and frees the cycle.

Everything reachable from a module-level name, a class attribute or a long-lived object lives as long as they do. A cache, a registry of callbacks or a list of past events that is only added to grows with the work done, which is why a long-running service shows such growth and a short script does not.

Key idea

Memory that keeps growing is held by something reachable, such as a module-level cache, or by a cycle that waits for the garbage collector.

Checkpoint

Check: What keeps memory alive

  1. Question 1 of 2 Which of these is freed as soon as the program stops using it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A list that only a local variable referred to, once its function returns

    CPython frees an object when its reference count falls to zero. Objects reachable from a module-level name live on, and cycles wait for the garbage collector.

  2. Question 2 of 2 A dict at module level caches every result that a function computes. Why does it cause trouble in a long-running service and not in a short script?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It lives as long as the program and grows with the work done, so a service that runs for days keeps growing

    A cache, a registry or a list of past events that is only added to grows with the work done. In a short script the work ends early; in a service it never does.

sys.getsizeof() measures one object

sys.getsizeof() returns the size of one object, without the objects it refers to, so for a container it measures the container alone. tracemalloc counts every allocation, so the two disagree whenever a container holds objects of its own:

A million integers in a list and in an array, measured both ways Python · list_vs_array.py
import sys
import tracemalloc
from array import array

N = 10**6


def traced(build):
    """Build an object while tracemalloc runs; return it with the bytes it took."""
    tracemalloc.start()
    before = tracemalloc.get_traced_memory()[0]
    obj = build()
    after = tracemalloc.get_traced_memory()[0]
    tracemalloc.stop()
    return obj, after - before


numbers, list_bytes = traced(lambda: list(range(N)))
packed, array_bytes = traced(lambda: array("i", range(N)))

print(f"{64 if sys.maxsize > 2**32 else 32}-bit build, {N:,} integers")
print(f"list:       getsizeof {sys.getsizeof(numbers):>10,} B   traced {list_bytes:>11,} B")
print(f"array('i'): getsizeof {sys.getsizeof(packed):>10,} B   traced {array_bytes:>11,} B")
print(f"one int object: {sys.getsizeof(N - 1)} B, one reference: {8 if sys.maxsize > 2**32 else 4} B, one array item: {packed.itemsize} B")

Output

64-bit build, 1,000,000 integers
list:       getsizeof  8,000,056 B   traced  39,991,384 B
array('i'): getsizeof  4,091,948 B   traced   4,091,980 B
one int object: 28 B, one reference: 8 B, one array item: 4 B

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 list_vs_array.py

Version note

The browser runner is a 32-bit build: a reference takes 4 bytes there and a small int 16, so the list and its integers take about half the memory of the 64-bit run, while the array of 4-byte items stays the same size.

In your browser, Pyodide 314.0.7 (CPython 3.14.2) prints:

32-bit build, 1,000,000 integers
list:       getsizeof  4,000,028 B   traced  19,994,748 B
array('i'): getsizeof  4,091,908 B   traced   4,091,924 B
one int object: 16 B, one reference: 4 B, one array item: 4 B

On the 64-bit build, getsizeof() reported 8 MB for the list: one 8-byte reference per item. Building it took about 40 MB, because each of the million integers is an object of its own, which tracemalloc counted at 32 bytes. An array("i") stores the numbers themselves, 4 bytes each, so both measurements agree for it, at a tenth of the list’s real size. Step through what each measurement looked at:

Interactive · step by step What getsizeof() and tracemalloc count for a million integers Step through the two measurements of list_vs_array.py on 64-bit CPython. A box shows the size measured for it.
What getsizeof() and tracemalloc count for a million integers, step 5 of 5. Showing the list object (traced: 39,991,384 B), 1,000,000 int objects (about 32 bytes each), array('i') (both: about 4.09 MB). In focus: the list object, array('i').one reference eachthe list objecttraced: 39,991,384 B1,000,000 int objectsabout 32 bytes eacharray('i')both: about 4.09 MB

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. The list alone. getsizeof() looks at the list object only, its header and one 8-byte reference per item, and reports 8,000,056 bytes.
  2. What it points to. Every reference points to an int object of its own. getsizeof() of the list never follows the references, so none of these objects is in its number.
  3. Everything built. tracemalloc counted every block that building the list allocated, 39,991,384 bytes, the list itself plus about 32 bytes for each int, 4 more than the 28 that getsizeof() reports for one.
  4. The array. array('i') stores the numbers themselves, 4 bytes each, in its own buffer, so there is nothing more to follow. getsizeof() says 4,091,948 bytes and tracemalloc 4,091,980.
  5. The difference. For the same million numbers the list and its integers take almost ten times the memory of the array, and getsizeof() saw only a fifth of the list's real cost.

Data Storage Converter tracemalloc's KiB and MiB are binary units (1 KiB = 1,024 bytes), while many tools report decimal MB: convert between them exactly.

Checkpoint

Check: Shallow sizes

  1. Question 1 of 2 For the million integers, getsizeof() reported 8,000,056 bytes for the list, and tracemalloc counted 39,991,384 bytes for building it. About how many bytes did each int object add?

    Type a number.

    A hint

    Subtract the list's own size, then divide by a million.

    Show the answer: Question 1 of 2

    Answer: 32 bytes (anything from 31 to 33 counts)

    (39,991,384 − 8,000,056) ÷ 1,000,000 ≈ 32 bytes for each int object, on top of the 8-byte reference to it that the list holds.

  2. Question 2 of 2 Why do getsizeof() and tracemalloc agree for an array('i') of a million numbers?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The array stores the numbers themselves, so there are no other objects to count

    A list holds references to objects of their own, which getsizeof() does not count. An array holds the raw numbers in its own buffer, so its getsizeof() is close to the truth.

Each memory question has its own tool

  • tracemalloc answers where memory was allocated and what grew between two moments.
  • sys.getsizeof() gives the size of one object without what it refers to.
  • gc.get_referrers() lists the objects that refer to a given object, to find who keeps it alive.
  • gc.collect() runs the garbage collector at once and returns how many unreachable objects it found.
  • functools.lru_cache(maxsize=…) keeps a fixed number of recent results instead of every result.
  • Generators produce one item at a time, so a generator pipeline: A chain of generator functions in which each stage takes the stage before it as its input and yields its results to the next, so items flow through one at a time and memory stays flat whatever the size of the input. More in “Generator pipelines, yield from and send” holds one item instead of the whole list.
  • __slots__ and arrays store many small records or numbers with less memory per item.
Three ways to look at memory, by the question each one answers
Criterion tracemallocsys.getsizeof()gc.get_referrers()
The question where was it allocated, and what grew?how big is this one object?who still refers to this object?
What it sees every block Python allocates, with its linethe object alone, not what it refers tothe objects that hold a reference to it
Use it when memory grows and you need the lineyou want a quick size of one objectan object should be gone but is not
Python Online Compiler Run cache_growth.py with more users or a smaller history list, and watch how the growth between the two snapshots changes.

Checkpoint

Check: A tool for each question

  1. Question 1 of 2 An object should be gone, but it is still in memory. Which tool lists what still refers to it?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: gc.get_referrers()

    tracemalloc finds where the memory was allocated, and gc.get_referrers() finds who holds the object now. A leak raises both questions.

  2. Question 2 of 2 A batch job uses more memory after each file it processes. Which first step shows which line's allocations grew between two files?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Take a tracemalloc snapshot after one file and another after the next, and compare them

    Growth between two moments is what two snapshots show. Once the line is known, gc.get_referrers() helps find the object that keeps the memory.

Check yourself

Nine short questions test snapshots, references, shallow sizes and the choice of a tool, and each new question depends on how you answered the last ones.

Practice that adapts to you

Practise this lesson

9 questions about 11 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 compare_to() between two snapshots of a service puts "handlers.py:10, +3,600 KiB in +25,000 blocks" first. What does that tell you?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: Memory allocated by line 10 between the two snapshots is still alive, so something keeps those objects

    A snapshot holds the memory blocks that are alive when it is taken, so a difference between two snapshots is memory that was allocated in between and not freed. The next question is what still refers to those objects: a cache, a module-level list or a reference cycle.

  2. Question 2 of 9 Which call returns the current size and the peak size of the memory that tracemalloc traces?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: tracemalloc.get_traced_memory()

    get_traced_memory() gives the current and the peak size in bytes; a snapshot and its statistics tell you where that memory is.

  3. Question 3 of 9 Which of these stays in memory after the program has dropped every name for it, until the garbage collector runs?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Two objects that refer to each other

    CPython frees an object as soon as its reference count reaches zero. In a cycle, each object keeps the other's count above zero, so reference counting alone never frees them; the cyclic garbage collector finds such groups that nothing outside refers to and frees them.

  4. Question 4 of 9 Which of these make the memory of a long-running service grow with every new user?

    Choose every answer that is right.

    Show the answer: Question 4 of 9

    Answer:

    • Callbacks of every opened page added to a module-level list and never removed
    • A dict at module level used as a cache and never emptied
    • functools.lru_cache(maxsize=None) on a function called with each user's id

    The first three keep a reference to something per user for as long as the program runs: a dict, an unbounded cache and a registry of callbacks all only grow. A list that the caller drops has no references left, so reference counting frees it at once.

  5. Question 5 of 9 On 64-bit CPython, sys.getsizeof() reports about 8 MB for a list of a million integers, while tracemalloc counts about 40 MB for building it. Which is right?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: Both are, about different things: getsizeof() counts the list's own array of references, tracemalloc also the integers

    sys.getsizeof() is shallow: a list's size covers its header and one 8-byte reference per item, not the objects those references point to. Each of the million int objects is a separate allocation, which tracemalloc counts, so the total is about five times the list alone.

  6. Question 6 of 9 What does sys.getsizeof() of a list count?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: The list object: its header and its array of references, with any spare room

    For a container, getsizeof() measures the container alone: on a 64-bit build, 8 bytes per reference plus a header and some spare room.

  7. Question 7 of 9 What does gc.collect() return?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: How many unreachable objects it found

    gc.collect() runs the garbage collector now and returns how many unreachable objects it found.

  8. Question 8 of 9 A function decorated with @functools.lru_cache(maxsize=None) is called with every new user's id, and memory keeps growing. What do you change?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: Give the cache a bound with lru_cache(maxsize=…), so that it keeps a fixed number of recent results

    maxsize=None keeps every result for as long as the program runs. A bound keeps the memory in proportion to the bound, not to the number of users.

  9. Question 9 of 9 A script reads every line of a large log into a list and then counts the slow requests. What keeps its memory small, however long the log is?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: A generator that hands one line at a time on to the counting

    Generators produce one item at a time, so a pipeline holds one line instead of the whole log.

Tracing growing memory to its line and its holder Pro: opens with a Pro, Premium or Ultimate pass

A snapshot difference names the line that allocated the memory; to fix it you need the call path that led there and the reference that keeps the memory alive. This part measures growth over time, follows an allocation through its callers, finds the object that holds it and bounds a cache.

In this part:

  • Growth that never stops, measured batch by batch
  • Which callers allocated it: tracebacks with several frames
  • What tracing costs, measured
  • A cycle that waits for the collector
  • A checklist for memory that keeps growing

1 diagram · 4 runnable examples (Python) · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Memory budgets in numbers: records, peaks and bounded caches Pro: opens with a Pro, Premium or Ultimate pass

Once the leak is gone, the next question is how much memory the work needs at all. This part measures the cost of one record in seven representations, compares the peak of a list with a generator pipeline, and sizes a cache from a budget.

In this part:

  • Bytes per record in seven representations, measured
  • The peak of a list against a generator pipeline
  • Sizing a bounded cache from a memory budget
  • Exercise: a session store that stops growing
    • Stop a session store from growing without limit

2 runnable examples (Python) · 1 exercise with sample tests · about 11 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    Why does sys.getsizeof() report much less memory for a list of objects than the list really uses?

    Because sys.getsizeof() is shallow: it returns the memory of the object itself, not of the objects it refers to. For a list, that is its header and its array of references, 8 bytes per item on a 64-bit build, plus some spare room. The items are separate objects with memory of their own: in this lesson, a list of a million integers measured 8 MB with getsizeof() and about 40 MB with tracemalloc, because each integer is an object of about 32 bytes. Strings, dicts and instances in a list add their sizes in the same way, and an object shared by several containers exists only once. To see what a structure really costs, trace its construction with tracemalloc. For plain numbers, the array module stores the values themselves, so its getsizeof() is close to the truth.

  2. Mid to senior

    A Python web service's memory grows by about 200 MB a day until it is restarted. How do you find the cause, and how do you prove the fix?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A function decorated with functools.lru_cache(maxsize=None) caches product prices by product id and date, and memory grows every day. How do you choose a bound, and what do you measure to show that the bound is right?

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A nightly job that processes a 4 GB CSV file is killed for running out of memory on an 8 GB machine. How do you find what takes the memory, and how do you make the job's memory independent of the file size?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • tracemalloc.start(), then take_snapshot() and statistics("lineno"), shows the lines that hold the most memory; compare_to() between two snapshots shows what grew.
  • Memory that grows is memory that something still refers to: a cache, a module-level list or registry, or a reference cycle waiting for the garbage collector.
  • sys.getsizeof() is shallow: a list of a million integers and the integers in it take about five times what it reports for the list.
  • Bounded caches, generators, __slots__ and arrays keep memory proportional to what a program needs at once.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.