Embercode
✕
References and MemoryLesson 8 of 15

45 min read

Shared ownership and weak references in Python

Why it matters

Some objects have more than one user. A photo can appear on a profile page and in a list at the same time. The question is who keeps the photo alive. In Python the answer is usually "whoever holds a reference", and that can be surprising. A cache, a list of listeners or a back-link can keep an object alive long after the program needs it. The opposite problem also exists: a program holds a reference to an object that it expects to be gone, and it sees stale data.

Python gives you the weakref module for the cases where a holder should look at an object without keeping it alive. This lesson shows when to use it, and what the result looks like.

This lesson assumes you know what a reference is (lesson 2) and what a leak is (lesson 4). Lesson 9 covers the tools that find leaks. This lesson covers the design choice that prevents them.

Owners and viewers

An owner is a holder that keeps an object alive. As long as one owner exists, the object stays in memory. A viewer is a holder that can look at an object but does not keep it alive. A viewer may find that the object is gone.

Think of a library book. A person who has borrowed it is an owner: the book stays out of the shelf while they hold it. A person who only knows the shelf number is a viewer. They can walk to the shelf, but they cannot stop the book from being withdrawn.

In Python, an ordinary name, a list slot or an attribute is an owner. A weakref.ref is a viewer. The rest of this lesson shows what that means in code.

Counting owners

CPython keeps a count of the references to each object, as lesson 4 explained. The count goes up when a name, a list slot or an attribute starts to point at the object, and it goes down when that reference is removed. The function sys.getrefcount shows the count. It is for learning and debugging, not for program logic, and it always reports one more than you expect, because passing the object to the function adds a reference.

Python · editable

Type nothing into the Input box for this program.

refs with one name: 2
after two list slots: 4
after pop: 3
size 1 refs to the last box: 2
10

Trace the counts. The name x is one owner, and the argument to sys.getrefcount is the second reference, so the first line is 2. Putting x into the list twice adds two more owners, which gives 4. The call v.pop() removes one list slot, so the count is 3. After del x, the list is the only owner. The count shown is 2, because sys.getrefcount adds its own argument reference. The object is still alive, so v[0].v prints 10.

When the last owner goes, CPython frees the object at once. The next section shows that with a weak reference.

Weak references: a viewer that does not own

A weakref.ref points at an object without adding to the count. Calling the reference with no arguments gives back the object, or None if the object has been freed. A viewer must always be prepared for None.

Not every object can be weakly referenced. Lists, strings and plain object() instances do not allow it. Class instances do, which is why most examples in this lesson use a class.

Python · editable

Type nothing into the Input box for this program.

alive while a name holds it: True
alive while held: True
gone after the last owner is removed: True

The reference r does not count as an owner. After del obj, the name held is still an owner, so the object lives. After del held, nothing owns the object, so CPython frees it at once, and r() returns None.

Call the reference first and check the result before you use it. Calling it once and storing the answer keeps the check and the use on the same object:

owner = r()
if owner is not None:
    print(owner.v)

Do not check r() and then call it again later. The object may be freed between the two calls.

When owners point at each other

Shared ownership has one trap: a cycle. A cycle is a chain of references that leads back to where it started. Suppose two objects each hold a reference to the other. Each one keeps the other alive, so neither count can reach zero by itself.

The cycle collector from lesson 4 finds cycles like this one and frees them when nothing outside them reaches either object. The next program creates the cycle and then removes the outside names. The __del__ method prints when each object is freed.

Python · editable

Type nothing into the Input box for this program.

names removed
destroy A
destroy B
after collect

The two objects keep each other alive, so they are not freed when the names are removed. The call gc.collect() finds them as garbage, and both are freed. The order of the two destroy lines can differ between Python versions. In CPython, a cycle is not a permanent leak, but its memory waits until the cycle collector runs.

Cycles still matter for design, for two reasons. The first is that a cycle delays freeing. The second is that a cycle can hold large data alive for longer than the program needs. A common shape is a parent that owns its children, and a child that needs to find its parent. If both sides hold strong references, the design is hard to follow, and the objects are freed later than they should be.

The fix is to choose one direction to own. The owner holds a strong reference to the child. The child holds a weak reference back to the owner. The owner's lifetime controls the child's lifetime, not the other way round.

Python · editable

Type nothing into the Input box for this program.

parent of C is P
destroy P
parent of C after P is gone: None
destroy C

The parent owns the list of children, and the child points back to the parent through weakref.ref. When the name p is deleted, the parent has no owners left, so it is freed at once, and its __del__ prints destroy P. The child is still alive, because the name c holds it, and its parent reference now returns None. The child reports that its parent is gone, instead of keeping the parent alive by accident. The last line, destroy C, appears when the program ends and the name c goes away.

A weak back-link also makes the design easier to read. Anyone who sees weakref.ref(parent) knows that the child does not own the parent.

A cache that does not keep its items alive

A cache keeps a copy of something so that the next request is fast. The copy is useful only while somebody still uses the original. A cache that holds a strong reference to every item grows without limit, and that growth is a leak. A cache built on weak references holds an item only while another part of the program holds it too. When nothing else uses the item, it is freed, and the cache loads it again the next time someone asks.

The weakref module has dictionary types for this. A WeakValueDictionary holds its values weakly. When a value has no strong owner left, the dictionary removes its entry.

The next program compares a plain dictionary with a WeakValueDictionary. Each image is about one megabyte, so keeping them all alive costs memory.

Python · editable

Type nothing into the Input box for this program.

strong entries: 5
weak entries: 0
same object while held: True
weak entries with hero: 1

Trace the output. The loop asks each cache for five images. The return values are thrown away, so no name holds them.

The plain dictionary keeps every image, because a dictionary holds strong references. Its length is 5, and it grows with every new name.

The WeakValueDictionary stores its values weakly. Once the loop ends, nothing else holds the five images, so CPython frees them at once, and their entries are removed. Its length is 0.

The last lines show the behaviour a cache needs. While the program holds held, a second request for "hero" returns the same object, so held is again is True. The check is compares identity, which means "is this the same object", not "do these look alike".

Why caches use weak references

The trade-off is that the cache cannot promise to keep anything. An entry can disappear at any time when no strong reference exists. The code must still work when a lookup misses. In the example, a miss means building a new Image, which costs time but gives the right answer.

A weak cache is a good fit for data that is cheap to rebuild but expensive to keep, such as decoded images, parsed templates or shared configuration. It is a poor fit for data that exists only in the cache. If the cache holds the only copy of something, the item disappears without an error, and the data is lost.

WeakKeyDictionary and its limits

A WeakKeyDictionary holds its keys weakly. When a key has no strong reference left, the dictionary removes the entry. This is useful for attaching extra information to objects that someone else owns.

Python · editable

Type nothing into the Input box for this program.

1
0

The entry goes away as soon as the key is freed. The key must be a class instance, or another type that allows weak references.

Strings and numbers do not allow weak references, so they cannot be keys in a WeakKeyDictionary:

Python · editable

Type nothing into the Input box for this program.

TypeError: cannot create weak reference to 'str' object

The same error appears for plain object() instances. Use a small class for keys that must be weak.

Common mistakes

Keeping a strong reference by accident. A list, a dictionary or an attribute that holds an object keeps it alive. A cache built with a plain dictionary never frees its items. Use WeakValueDictionary when the cache should not be the owner.

Calling a weak reference without checking. The call r() returns None after the object is freed. Code that uses the result without a check fails with an AttributeError on None. Store the result of the call in a name, check it with is not None, and then use it.

Using a weak key for a shared value. Strings and integers do not allow weak references, so a WeakKeyDictionary cannot hold them at all. Use a class instance for a weak key.

Assuming a cycle is a permanent leak. CPython frees cycles, but only when the cycle collector runs. A cycle can hold memory for longer than you expect, and a design with a weak back-link avoids the delay.

Treating a weak cache as a size limit. A weak cache empties itself only when the objects lose their owners. A program that keeps many items alive will still fill memory with cache entries. If you need a fixed maximum size, use a cache with an explicit size limit, such as functools.lru_cache(maxsize=...).

Expecting an entry to vanish at a precise moment in another implementation. The timing of freeing depends on the interpreter. CPython frees most objects at once, but another implementation may keep them longer. Write code that works either way.

Practice

Exercise 1: predict the counts

Read this program and write the lines it prints, before you run it.

import sys
 
class Box:
    pass
 
x = Box()
v = [x, x]
print(sys.getrefcount(x))
v.pop()
print(sys.getrefcount(x))
del x
print(len(v), sys.getrefcount(v[0]))

Show the answer

The program prints 4, then 3, then 1 2. The name x and the two list slots are three owners, and the argument to sys.getrefcount adds one, so the first line is 4. The pop removes one slot, so the next count is 3. After del x, the list holds one owner, and the argument adds one more, so the count is 2.

Exercise 2: choose the owner

A Folder holds a list of File objects. Each File must be able to find its Folder. Which link should be a strong reference and which should be a weak reference, and why?

Show the answer

The Folder should hold strong references to its files, because the folder owns them. Each File should hold a weakref.ref to its Folder. If both sides used strong references, each would keep the other alive, and the cycle would wait for the cycle collector before anything is freed.

Exercise 3: a reference held by a name

A program does this:

obj = Image("hero")
r = weakref.ref(obj)
held = obj
del obj
print(r() is None)

Will the last line print True or False? Explain why.

Show the answer

It prints False. The name held is a strong reference to the image, so the object is still alive, and r() returns it. If the program then ran del held, the last line would print True.

Exercise 4: a dictionary of dead references

The line print(len({"a": weakref.ref(Box(5))})) prints 1, even though the object it refers to is already freed. A colleague says the dictionary holds one image. Explain what is wrong with that conclusion.

Show the answer

The colleague is wrong. The dictionary holds one weakref.ref object, and that reference points at nothing, because the Box was freed as soon as the expression finished. Calling the reference gives None. A plain dictionary of weak references can look full when it holds no live objects. A WeakValueDictionary removes those entries on its own.

Exercise 5: a viewer that outlives its owner

A program creates a weakref.ref to an object, deletes the last strong name, and then runs r().name without checking the result. What happens?

Show the answer

The object was freed, so r() returns None. Reading .name from None raises AttributeError: 'NoneType' object has no attribute 'name'. The code must store the result of r() in a name and check it with is not None before it reads an attribute.

Summary

  • An owner is a holder that keeps an object alive. A viewer, such as weakref.ref, does not.
  • sys.getrefcount shows the number of owners, plus one for its own argument. Do not use it in program logic.
  • A cycle of strong references waits for the cycle collector before it is freed. A weak back-link lets the owner's lifetime control the child's.
  • A WeakValueDictionary is a cache that does not keep its items alive. Its code must rebuild a missing item, because the item may be gone.
  • A WeakKeyDictionary needs keys that allow weak references, which excludes strings, numbers and plain object() instances.
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.

← Drawing reference diagrams for Python lists