Embercode
✕
References and MemoryLesson 4 of 15

50 min read

Garbage collection, copies and why Python has no pointer arithmetic

Why it matters

Interviews often follow the questions about names and objects with three more: "who frees memory in Python?", "how do you copy an object?" and "can you do pointer arithmetic in Python?" These questions share one theme. Python manages memory for you, and that choice changes how you copy data and how you avoid leaks. The linked-list problems in the next modules also depend on the rules in this lesson, so the time you spend here pays off there.

By the end of this lesson you will be able to say when an object becomes garbage, find a leak in a small program, copy a structure of objects correctly, and explain the trade-off behind Python's design without repeating a slogan.

First principle: reachable means still in use

Lesson 1 said that an object stays in memory while a name refers to it. Here is the precise version. An object is reachable if you can get to it by following references, starting from the places a running program can always see. Those starting places are called roots. The roots are the names in the frames of running functions, the names at module level, and a few internal references that the interpreter keeps for itself.

Anything you cannot reach from a root is useless to the program. The program can never look at it again, so keeping it has no value. Such an object is called garbage.

The garbage collector is the part of the interpreter that finds garbage and reclaims the memory it uses. You never call free or delete, as you would in C. Your job is to make sure that objects you no longer need really are unreachable.

An everyday analogy helps. A library keeps a book on the shelf as long as at least one member has it on a reading list, or on a loan card. When no list and no card mention the book, the librarian removes it. The librarian does not ask the book whether it is needed. The book is needed exactly when someone can reach it.

How CPython frees memory

CPython, the usual Python interpreter, uses two methods together. Most objects are freed by reference counting. Each object keeps a count of how many references point at it. When a name is moved away or deleted, the count goes down. When the count reaches zero, nothing can reach the object, and it is freed at once.

Reference counting cannot free a cycle, which is a group of objects that point at each other. Each object in the cycle keeps a count above zero, even when nothing outside the cycle reaches any of them. For cycles, CPython runs a separate cyclic garbage collector, from the gc module. It searches for groups of objects that only refer to each other and frees them.

Here is a short sequence that shows both cases. It creates a single object and removes the only reference to it. Then it creates two objects that point at each other.

Python · editable

Type nothing into the Input box for this program.

before None
freed single
after None
names deleted
freed A
freed B
after collect

The first object is freed the moment s stops pointing at it, because its count reached zero. The method __del__ runs just before the memory is released. The two objects in the cycle are not freed when del runs, because each still has a count from the other. They are freed only when gc.collect() searches for the cycle.

This is a CPython detail. Other Python implementations may free objects at other times, and in some of them __del__ may run much later. Do not write a program that depends on the exact moment of freeing.

Key facts about the collector

These facts are the ones interviewers expect you to state.

  • Most freeing is automatic and immediate in CPython. When the last reference goes, the object is freed at once.
  • Cycles are freed by the cyclic collector. It runs from time to time on its own. Calling gc.collect() runs it on demand, but the program rarely needs that call.
  • Calling gc.collect() in normal code is almost always a mistake. It can slow the program, and it frees nothing that is still reachable.
  • Freeing costs time. Every assignment changes a count, and the cyclic collector pauses the program briefly while it works. A program that creates huge numbers of short-lived objects in a hot loop spends more time on that work.
  • Python can still leak memory. A leak is an object you no longer need but still reach from a root. The collector correctly keeps it, so the process keeps growing.

The last point is the one that surprises most beginners. A garbage collector frees memory you have forgotten, but it cannot free memory you still reference. Keeping a reference is a decision, and a leak is what happens when the decision was not made on purpose.

Tracing reachability by hand

You can predict what the collector will keep without running anything. The method is a small walk, and it works on any picture of memory you can draw.

Step 1: list the roots. These are the names in every running function, including the parameters, and the names at module level. Put a mark next to each object that a root refers to directly.

Step 2: follow every reference from a marked object. Mark each object it leads to. Repeat until no new object gets a mark.

Step 3: everything unmarked is garbage. It can be reclaimed, even if other garbage refers to it.

Take a concrete case. A module-level name history refers to a list. The list holds three request records. Record 2 refers to a user object. A local name in the running function refers to record 3. Nothing else exists. Walking from the roots marks the list (through history), records 1, 2 and 3 (through the list), the user object (through record 2), and record 3 again, which is already marked. Every object is marked, so nothing is garbage, even though the local name will disappear when the function returns. The history list keeps everything alive for as long as the program runs.

Now make the list local. When the function returns, the roots are gone, and the walk marks nothing. The whole structure becomes garbage at once. The same objects, a different root, and the outcome changes completely. This is why a leak is always a question about roots.

Watching a leak happen

The next program simulates a server that handles requests. Each request creates a buffer of 1000 numbers, and the buffer is stored in a list named cache at module level. The module-level name is a root, so every buffer stays reachable until the list lets it go. The class Buffer holds the list of numbers, so that a weakref can point at it later in this section.

Python · editable

Type nothing into the Input box for this program.

cached buffers: 3
first id: 1
after clear: 0

Trace the program. Each call to handle_request creates a local name buffer that points at a new object. When the function returns, that name disappears. But the object is still in cache, and cache is a module-level name, which is a root. So all three buffers stay reachable after their functions have finished. The collector keeps them because the program can still reach them through cache.

When cache.clear() runs, the list drops its references. The three buffers are now unreachable, and each one is freed as its count reaches zero. The output line after clear: 0 shows the list is empty. It does not show that the memory was freed, so the next example uses a weak reference to check that.

A weak reference, made with the weakref module, points at an object without keeping it alive. Calling the weak reference gives back the object, or None if the object has been freed. It lets a program watch an object without holding on to it.

Python · editable

Type nothing into the Input box for this program.

alive after the function returned: True
alive after clear: False

The first line is True because the object is still in cache. The second line is False because cache.clear() removed the last strong reference, and CPython freed the object at once.

In a real server, the same pattern hides in a module-level dictionary that caches responses, or in a list of callbacks that is never cleaned up when a user logs out. Each entry is small, but the total grows for as long as the server runs. Eventually the process runs out of memory. Python then raises MemoryError, or the operating system stops the process, while the program keeps trying to allocate more.

The garbage collector did its job in that case. Every buffer was still reachable, so none could be freed. The bug is in the program's decision to keep them.

How to fix a leak

Ask two questions. First, which root reaches the object? The answer is often a module-level name, a long-lived object such as a session, or a callback registered with another object. Second, should that root still reach the object? If not, remove the reference when the work is finished, or limit the size of the collection. A cache with a maximum size, or one that expires old entries, cannot grow forever. The standard library's functools.lru_cache decorator does the first of these: it keeps only the most recently used results, up to a size you set.

How CPython organises the cyclic collector

Reference counting frees most objects as soon as they stop being used. The cyclic collector has to search for cycles, which costs more. To keep that search cheap, CPython sorts the objects it finds into three generations. New objects start in generation 0. An object that survives a search moves to the next generation. The younger generations are searched more often, because most objects die young, and the older generation is searched less often.

For a Python programmer, the practical meaning is short. Short-lived objects are cheap, so do not avoid them out of fear. Objects that are kept by mistake move to the older generation and stay there, using memory until a full search runs. A leak of that kind shows up as slow growth, which is why the debugging walkthrough below looks at object counts over time.

The exact details depend on the interpreter and its version. The rule that holds across all of them is the one in this lesson: an object is kept while something reaches it, and freed after nothing does.

The cost of freeing

Freeing is not free. Reference counting changes a count on every assignment, and the cyclic collector pauses the program briefly while it searches. Most programs do not notice this. A program that creates millions of short-lived objects in a loop feels it, because each object adds work.

Two practical habits follow. First, do not create objects you do not need inside a hot loop. Build a string once with "".join(parts) instead of joining pieces again in every pass, and store numbers in a list or an array instead of making a new object for each one. Second, do not call gc.collect() to speed things up. It rarely helps, and it can make a program slower.

An interviewer may ask, "how would you reduce garbage in a loop?" A good answer names the object created in the loop, and says whether it can be moved outside the loop or reused. A poor answer says "call gc.collect()".

Shallow copy and deep copy

Now the second theme: copying. A copy is a new object with the same data. There are two kinds. The difference matters once an object holds other objects.

A shallow copy creates a new outer object, but the inner objects are shared. A deep copy creates the outer object and also copies every inner object, so the two structures are fully independent.

An analogy: copying a folder of shortcuts gives you a new folder with the same shortcuts. Both folders still open the same original files. Copying the files themselves gives you a second set of files, and changing one set does not affect the other.

The next program makes both kinds of copy and then changes them, so you can see which changes reach the original.

Python · editable

Type nothing into the Input box for this program.

99
4
[[99, 2], [3, 0]]

Trace each step. grid.copy() makes a new outer list of two references. The two references lead to the same inner lists as before. So shallow[0][0] = 99 changes the inner list that grid[0] also leads to. The first output line is 99, which proves the sharing.

Next, the deep copy. The list comprehension [row[:] for row in grid] makes a new outer list, and each row is sliced with [:]. Slicing a list from start to end makes a new list with the same items. Each new row is a separate list. At this point deep[0][0] is 99, because the deep copy was made after the shallow change, which had already altered the shared inner list.

The line deep[1][1] = 0 changes only the new copy. grid[1][1] stays 4, which is the second output line. The last line shows the deep copy with its changed value.

Two facts follow from the program. Slicing a list of numbers, such as items[:], makes a full copy, because the items are numbers, and a number is immutable so sharing it is harmless. Slicing a list of lists makes only a shallow copy, because the items are references. The same is true for list(items), items.copy() and the copy.copy function. Each of them copies the references and stops there.

For a deep copy of any depth, use copy.deepcopy:

Python · editable

Type nothing into the Input box for this program.

2 -1

The deepcopy function copies the outer list, the inner lists and everything they hold, and it remembers objects it has already copied, so a cycle does not cause endless copying.

Copying objects of your own class

For your own classes, Python has two clean tools. The first is to write an __init__ that takes another object of the same class and copies its attributes. The second is to define a __copy__ method, which the copy.copy function calls. Use them when you want a copy that behaves in a specific way for your objects.

Python · editable

Type nothing into the Input box for this program.

Asha(19) Asha(20)
Asha(21)

The method __copy__ builds a new Student with the same name and age, so b.age = 20 leaves a alone. The name c is different, because c = a copies the reference, not the object. So c.age = 21 changes the object that a points at, and the last line shows 21. The method __repr__ controls how the object looks when it is printed. It has nothing to do with copying.

The name attribute is a string, and a string is immutable, so sharing it between the copies is safe. If an attribute were a mutable object such as a list, __copy__ would need to copy that list too, which makes it a deep copy of that attribute. The method __deepcopy__ controls that case, and the copy.deepcopy function calls it.

Copy methods compared

Python gives you several ways to copy, and they differ in one question: does the copy share the inner objects? The table lists the common choices for a list that holds other objects.

MethodWhat it copiesInner objects shared?
items[:], items.copy(), list(items)Only the referencesYes, shallow
copy.copy(items)Only the referencesYes, shallow
[row[:] for row in items]Each inner list, one level downNot below that level
copy.deepcopy(items)Everything, at every levelNo, deep
__copy__ on your classThe attributes you chooseDepends on each attribute

The safe default for a structure of mutable objects is copy.deepcopy, or a loop that copies each element. It costs more time, but it gives a result that does not change when the original changes. The fast methods are safe only when the elements are immutable, such as strings or numbers, because nobody can change an immutable element through either copy.

Reading the table, a rule emerges. A copy is only as deep as the code that makes it. If a call uses a copy method that stops at the references, the elements are shared, whatever the variable is called.

Deep copy of a structure of lists

Now apply the rule to a list of lists. The copy below is deep, because it copies each inner list as it goes.

Python · editable

Type nothing into the Input box for this program.

1 50
60

The first copy slices each row, so editing copy_one leaves original at 1. The second copy uses list(original). It makes a new outer list, but the outer list holds the same row lists. So editing the row through copy_two changes the row that original also holds, and the output is 60. The two copies look alike, but only one of them is safe to edit independently. Read the copy you make, and ask whether its items are shared.

Why Python has no pointer arithmetic

Some languages, such as C and C++, have pointers. A pointer is a variable that holds a raw memory address, and the language lets you do arithmetic on it. In C you can write p + 1 to move to the next element of an array, or *p to read the value at the address.

Python does not allow this. A Python reference is not a number you can see or change. It is not a memory address, and you cannot add to it. Here is what happens when you try to treat a reference as a number:

Python · editable

Type nothing into the Input box for this program.

TypeError: unsupported operand type(s) for +: 'Node' and 'int'

The interpreter refuses the operation when the program runs. This is the language's answer to the question "why no pointers?". Python protects the program from a whole class of bugs by not offering the tools that cause them.

The built-in function id returns a number that identifies an object while it lives. Two names have the same id only if they point at the same object. But id is for comparing identity, not for moving around in memory. Nothing in ordinary Python turns an id back into a reference, and you cannot add to it to reach the next object.

The standard library has a ctypes module for calling C code and reading raw memory. It is unsafe, it is meant for library authors, and normal programs do not need it.

Why did Python make this choice? There are three main reasons, and you should be able to give each one in a sentence.

Safety. Every index is checked. If the index is out of range, Python raises IndexError instead of quietly reading or writing somewhere else in memory. The message looks like this:

Python · editable

Type nothing into the Input box for this program.

IndexError: list index out of range

With raw pointers, the same mistake would read whatever bytes happened to be there, and the program would continue with wrong data.

Freedom for the interpreter. A program that cannot see addresses cannot depend on them. The interpreter can then choose how to lay out objects in memory, and it can change that choice in a new version without breaking old code.

Fewer bugs. Pointer errors cause a famous set of crashes: a pointer to memory that was already freed, which is a dangling pointer, and freeing the same memory twice. Python has no manual free, so these crashes cannot happen in ordinary Python code.

The cost is real. You give up some direct control, and some code runs slower than it would with hand-tuned memory. In return, a large category of crashes disappears. For most applications, that trade is worth it. Python programs that need speed often move the heavy work into a C library, such as NumPy, where the pointer details are hidden behind tested code.

Python still builds pointer-like structures out of references. A linked list is the standard example. Each node holds a value and a reference to the next node.

class Node:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next    # a reference to the next Node, or None at the end

The next attribute is the Python version of a pointer. You follow it with cur = cur.next, not with cur + 1. That difference is the whole lesson: a reference leads to an object, and you move along a chain of references by reading an attribute.

Linked lists: following and reversing references

Reversing a linked list is the classic interview problem about references. It needs three names at once, and the order of the assignments matters.

The next program builds a list of five nodes, reverses it and prints the result.

Python · editable

Type nothing into the Input box for this program.

5 4 3 2 1

Trace reverse with a list of three nodes, A holding 1, B holding 2 and C holding 3. Before the loop, prev is None and cur is A. In the first pass, next_node saves B, then A's next is set to None, prev becomes A and cur becomes B. The list is now split: A points to None, and B still points to C. In the second pass, B's next points to A, prev becomes B and cur becomes C. In the third pass, C's next points to B, prev becomes C and cur becomes None. The loop stops, and prev holds C, the new head.

The order of the first three lines inside the loop is the key point. The saved next_node must be taken before cur.next is changed, or the rest of the list is lost. Python lets you write the whole change on one line, cur.next, prev, cur = prev, cur, cur.next, because the right side is evaluated before any name is assigned. That form is correct, but the explicit version is easier to check while you learn. A quick check is to ask, before each line, whether the reference it needs is still reachable.

This is the same rule as lesson 3. The function returns prev, the new head, because the old head is no longer the first node. The caller stores the result.

Checking aliasing in a list

Lesson 2 explained aliasing. Linked lists show it clearly, because each node is a separate object that other names can reach. The next program builds a list, creates an alias to its middle node, and then cuts the list.

Python · editable

Type nothing into the Input box for this program.

nodes: 3
b.val now 20 a.next.val now 20
same object? True
nodes from a after the cut: 1

The name alias points at the same node as b, and that node is also a.next. So changing alias.val changes what all three names show. The check a.next is alias is True, which confirms that the names point at one object. Then a.next = None cuts the list. Nodes B and C are still in memory, because b, c and alias still reach them. Only the link from A is gone. If you also removed b, c and alias, both nodes would become garbage.

Common mistakes

Thinking that None always frees memory at once. Setting a name to None removes one reference. In CPython the object is freed at once if that was its last reference. If the object is part of a cycle, it waits for the cyclic collector. Other interpreters may free it later.

Calling gc.collect() to fix a problem. It searches for cycles, and it cannot free reachable objects. A leak keeps its objects reachable, so running the collector changes nothing.

Assuming items[:], items.copy() or list(items) copies the inner items. All three copy the references and stop there. For a list of lists, copy each inner list yourself, or use copy.deepcopy.

Expecting the __init__ of a class to make a deep copy. A copy function is deep only for the attributes it copies. An attribute that is a mutable object, such as a list, needs its own copy inside the copy function.

Saying "Python has no pointers". That is not quite right. Python has references, which lead to objects. What Python lacks is pointer arithmetic and direct access to addresses. The precise answer is more useful in an interview.

Forgetting the cycle case. Two objects that refer to each other are garbage when nothing outside them reaches either one. Do not write code that relies on a count of references to decide whether an object is needed.

Leaking through a module-level name. A module-level list or dictionary keeps its items reachable for the whole run of the program. Limit its size or remove entries when you are done.

Relying on __del__ for cleanup. The method __del__ runs when the object is freed, and the time of that is not guaranteed. To close a file or a connection at a known point, use a with statement, which calls a close method when the block ends.

Debugging walkthrough: "memory keeps growing"

A service has been running for a week, and its memory use climbs slowly. Restarting it makes the problem go away for a few days. Here is a method for finding the cause.

Step 1: confirm the growth is real. Look at memory use over time, and check that it rises after each batch of requests and does not fall back down. A healthy program goes up and comes back down as objects are freed.

Step 2: find what kinds of object are growing. The standard library's tracemalloc module records where memory is allocated. Start it at the beginning of the program, then take a snapshot after a batch of requests and another later. Compare the two snapshots. The file and line that keep growing are your suspects.

import tracemalloc
 
tracemalloc.start()
# ... run a batch of requests ...
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics("lineno")[:3]:
    print(stat)

The numbers and file paths you see depend on your program, so they will differ from run to run. What matters is which line keeps adding memory between snapshots.

Step 3: find what reaches the suspect. If the suspect creates a bytes or a dict that is stored in a collection, look for the collection that holds it. Check the module-level names and the long-lived objects, because those are the roots. Lesson 4 gave you the rule: if an object survives, something reaches it.

Step 4: decide on the fix. If the collection should not keep old entries, remove them when the work finishes, or put a size limit on it. If the entries must be kept, they belong in a bounded cache, such as one made with functools.lru_cache(maxsize=...).

Step 5: check the fix. Run the same load test again. The number of objects for the suspect line should now level off. A count that levels off after a large request load is what a bounded cache looks like.

The key lesson is that a garbage collector cannot tell you whether an object is still wanted. Only the program can decide that. Your job is to make the references match the decision.

Variations and edge cases

Weak references. A weak reference, made with weakref.ref, does not keep its object alive. When the only remaining references are weak, the object is freed. Caches sometimes use weak references for this reason. Use them only when you understand the trade-off, because the cached value may disappear at any time. A list cannot be weakly referenced, so the class Buffer in the example above has to be a class instance.

Closing resources. An object that holds a file or a network connection should release it in a known place. Use with open(...) as f: for files. The with block closes the file when it ends, even if an error happens inside it.

The None list. A variable can hold None, which is different from a list that holds None items. Check the variable before you use the list. [None, None] is a list of two empty slots, and None alone has no length.

A cycle that looks like a leak. Two objects that point at each other look as if they will leak, but the cyclic collector frees them when no root reaches either one. A real leak needs a root.

Interview questions on this topic

"Who frees memory in Python?" Mostly reference counting. When an object's last reference goes, it is freed at once. The cyclic garbage collector frees groups of objects that only refer to each other. You do not free memory yourself.

"What is the difference between a shallow and a deep copy?" A shallow copy duplicates the outer object and shares the inner objects. A deep copy duplicates the inner objects too, so the two copies are independent. Give an example with a list of lists.

"Can Python leak memory?" Yes. An object that is no longer needed but is still reachable from a root is never freed. Module-level collections are the usual cause.

"Why does Python not have pointer arithmetic?" For safety against out-of-range access, for the freedom of the interpreter to manage memory, and to avoid whole classes of crashes.

"How do you find a memory leak in a running Python program?" Take snapshots of memory with tracemalloc at two times, and compare them to find the line that keeps growing. Then find the root that keeps those objects alive. A leak is a root that should have let go.

"Why is gc.collect() not a fix for a leak?" It searches for cycles, but it cannot free reachable objects. A leak keeps its objects reachable, so running the collector changes nothing.

Follow-up you should expect: "How would you copy a list of mutable objects?" Use copy.deepcopy, or make a new list and add a copy of each element, using each element's copy method. A new list that holds the same elements is only a shallow copy.

Real-world uses

A common production leak is a list of callbacks. A user interface adds a callback to a shared event source each time a screen opens, but never removes it when the screen closes. Every closed screen stays reachable through the source, and its memory is never freed. The fix is to remove the callback on close, and the test for the fix is to open and close the screen many times while watching the object count.

Deep copies appear whenever data must be changed without affecting the original. A game saves a snapshot of the board before a move, so the player can undo it. The snapshot must be deep, or the move would change the saved board too. Many editors and drawing tools work this way.

Memory leaks in long-running servers are a common production problem. The usual cause is a cache, a registry or a list of callbacks that grows and is never cleaned up. Engineers find the leak with snapshots and fix it by bounding the collection or removing entries when the work is done.

Practice

Predict each answer before you run anything. Then check your answer by running the code.

  1. Write a function deep_copy(rows) that returns a list whose rows are independent copies. Editing one list's rows must never affect the other list.
  2. After a = Student(); b = a; a = None, is the Student object eligible for garbage collection? Explain using reachability. The answer depends on one more fact about b.
  3. Explain in two sentences why p + 1 on an object reference would be unsafe, without using the word "pointer" more than once.
  4. Predict the output of this code: g = [[1, 2]]; h = g.copy(); h[0][1] = 9; print(g[0][1])
  5. Write a Node class and a function count(head) that counts the nodes in a list. It must return 0 for None.
  6. In the reversal function, what would go wrong if you swapped the first two lines of the loop body, so that cur.next = prev ran before next_node = cur.next?
  7. A module-level dictionary stores a file for each user, and users are never removed. Name the root, the reason the collector keeps the files, and one fix.
  8. Write a Team class with a name attribute and a scores list attribute. Write a __copy__ method that makes a copy that is fully independent of the original.
  9. Explain why calling gc.collect() at the end of a program does not free the memory of a leak.
  10. Write a short paragraph, in plain words, that explains to a beginner why Python does not let a program add 1 to a reference.

Show the answer

  1. One possible answer:
def deep_copy(rows):
    return [row[:] for row in rows]

Each row is copied, so the new list holds new lists that share no data with the original. If the rows hold mutable objects, use copy.deepcopy(rows) instead.

  1. Not yet. a is set to None, but b still points at the object. The object is still reachable from the name b, so it is not garbage. It becomes eligible only after b is removed or the function that holds b returns.

  2. A reference is not a plain number, so adding to it would point at a random place in memory. That could read or write another object's data, which is unsafe and makes bugs hard to find.

  3. It prints 9. copy on a list of lists copies only the outer list. The row g[0] and h[0] are the same inner list, so changing h[0][1] changes g[0][1].

  4. One possible answer:

class Node:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next
 
def count(head):
    n = 0
    cur = head
    while cur is not None:
        n += 1
        cur = cur.next
    return n

The loop does not run when head is None, so the count is 0.

  1. The link from the current node would be overwritten before the code saves the rest of the list. The reference to the next node would be lost, so the nodes after the current one would become unreachable. The loop would stop early, and the output would lose values.

  2. The root is the module-level dictionary, because module-level names are always reachable while the program runs. The collector keeps every file because each one is reachable through the dictionary. A fix is to remove a user's entry when that user is removed, or to store files on disk and keep only a bounded cache in memory.

  3. One possible answer:

import copy
 
class Team:
    def __init__(self, name, scores):
        self.name = name
        self.scores = scores
 
    def __copy__(self):
        return Team(self.name, list(self.scores))

The name string can be shared, because it is immutable. The list is copied, because lists are mutable and must not be shared. Use copy.deepcopy if the scores hold mutable objects too.

  1. The call is only a request to search for cycles, and it searches only for garbage. More importantly, a leak keeps its objects reachable from a root. The collector never frees reachable objects, no matter how many times it runs. The fix is to remove the reference that keeps them alive.

  2. One possible answer: A reference is a label that says which object a name points at. Python does not let you add to that label, because adding to it would point at a random place in memory. Python keeps the label safe so that a program cannot read or write data it does not own. Use an index into a list, or an attribute of an object, to move from one item to the next.

What you now know

  • An object is garbage when no chain of references from a root reaches it. CPython frees most garbage at once, and a separate collector frees cycles.
  • A module-level name is a root, so an object stored in a module-level collection is never freed until the collection lets it go. That is a memory leak.
  • A shallow copy shares the inner objects. A deep copy copies them too. items[:], items.copy() and list(items) make shallow copies of a list of lists.
  • copy.deepcopy gives you a deep copy of any structure. A __copy__ method gives your own class a copy with the behaviour you choose.
  • Python has references, but no pointer arithmetic and no direct access to addresses. This is for safety, for the freedom of the interpreter and to avoid a whole class of crashes.
  • A linked list is built from references. To move along it, follow the next attribute, and save the next reference before you change a link.
Practice this topic: coding problems for References and Memory →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Passing objects to functions in Python