The errors you are actually going to hit, in the interpreter's own words.
36 entries across 25 exception types.
Every traceback on this page is real. Each snippet declares the exception
it expects, and the build runs it and checks: if it does not raise, or raises something else,
the build fails and this page is not written. So what you see under each snippet is what
CPython actually printed for exactly that code. Two things are normalised so the output is
the same on any machine — the temporary directory each snippet ran in becomes
example.py, and the interpreter's install location becomes
<python>. The type, the message, the line numbers and the
^^^ markers are untouched.
It will not even start
These are raised before any of your code runs, while Python is still reading the file. Nothing in your program executed, which is why a print() you added to debug it prints nothing. Look at the output of the panels below and notice what is missing: there is no "Traceback (most recent call last)" line on any of them, because there is no call stack yet. That absence is the quickest way to tell a file that would not compile from one that ran and broke.
SyntaxErrorA missing colon
A missing colon
raises SyntaxError
1
2def greet(name)
return "hi " + name
The traceback it really produced, when the build ran it
File "example.py", line 1
def greet(name)
^
SyntaxError: expected ':'
What it means. Python reached the end of the def line still expecting the colon that introduces the body. On 3.10 and later the message names the exact character it wanted, which is a large improvement on the older "invalid syntax".
The fix. Add the colon: def greet(name):. The same error covers if, for, while, class and try.
IndentationErrorA block header with no block
A block header with no block
raises IndentationError
1
2def greet(name):
return name
The traceback it really produced, when the build ran it
File "example.py", line 2
return name
^^^^^^
IndentationError: expected an indented block after function definition on line 1
What it means. Indentation is not style in Python, it is syntax: it is how the language knows what is inside the function. A def line promises an indented block underneath and this file did not deliver one.
The fix. Indent the body, conventionally by four spaces. If you genuinely want an empty block, write pass.
IndentationErrorIndentation that means nothing
Indentation that means nothing
raises IndentationError
1
2x = 1
y = 2
The traceback it really produced, when the build ran it
File "example.py", line 2
y = 2
IndentationError: unexpected indent
What it means. The opposite mistake: an indented line with nothing to be indented under. Python only accepts a deeper indent immediately after a line ending in a colon.
The fix. Line the statement up with the one above it. This often appears after deleting an if and forgetting its body.
TabErrorTabs and spaces mixed
Tabs and spaces mixed
raises TabError
1
2
3def f():
if True:
pass
The traceback it really produced, when the build ran it
File "example.py", line 3
pass
^
TabError: inconsistent use of tabs and spaces in indentation
What it means. One line here is indented with spaces and the next with a tab. They can look identical on screen while being different bytes, so Python refuses to guess which nesting level you meant.
The fix. Use spaces everywhere — four per level is the convention in PEP 8. Set your editor to insert spaces when you press Tab, and turn on whitespace rendering once so you can see the difference.
Names
Python looked for a name and either could not find it, or found it before it had a value.
NameErrorA name that does not exist
A name that does not exist
raises NameError
1
2total = 10
print(totl)
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print(totl)
^^^^
NameError: name 'totl' is not defined. Did you mean: 'total'?
What it means. Nothing called totl exists in any scope Python can see from here. Usually a typo, sometimes a name defined in another function, sometimes one you meant to import.
The fix. Read the suggestion. Since 3.12 the interpreter offers the closest name it does know, and here it is exactly right.
UnboundLocalErrorA local used before it was assigned
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 6, in <module>
bump()
~~~~^^
File "example.py", line 4, in bump
count = count + 1
^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
What it means. This one is genuinely surprising, and it is about scope rather than typing. Because bump assigns to count anywhere in its body, Python treats count as local to bump for the whole function — so the count on the right-hand side is the local one, which has no value yet. The global count = 0 is never consulted.
The fix. If you meant to read the outer value, do not assign to the name: return count + 1. If you really meant to rebind the global, declare global count — though a parameter and a return value is almost always the better design. Lesson 17 works through this.
Types
The value is the wrong kind of thing for what you asked. Python will not quietly coerce between types the way JavaScript does, which is why these surface early rather than as bad data later.
TypeErrorAdding a string to a number
Adding a string to a number
raises TypeError
1
2age = 30
print("age: " + age)
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print("age: " + age)
~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str
What it means. Python will not decide for you whether + here means arithmetic or concatenation, so it refuses. This is the error JavaScript does not give you: there, "age: " + 30 quietly produces a string and a genuine bug survives to production.
The fix. Convert explicitly with str(age), or better, use an f-string: f"age: {age}".
TypeErrorCalling something that is not a function
Calling something that is not a function
raises TypeError
1
2total = 5
total()
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
total()
~~~~~^^
TypeError: 'int' object is not callable
What it means. The parentheses mean "call this", and an integer is not callable.
The fix. Usually a name collision — a variable has shadowed a function of the same name. Check what the name is actually bound to at that point.
TypeErrorA missing argument
A missing argument
raises TypeError
1
2
3
4def area(w, h):
return w * h
area(3)
The traceback it really produced, when the build ran it
What it means. The function requires two positional arguments and got one. The message names the one it did not receive.
The fix. Pass it, or give the parameter a default in the definition. Lesson 15 covers why that default must not be a mutable value.
TypeErrorAn unhashable dict key
An unhashable dict key
raises TypeError
1
2seen = {}
seen[[1, 2]] = "x"
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
seen[[1, 2]] = "x"
~~~~^^^^^^^^
TypeError: cannot use 'list' as a dict key (unhashable type: 'list')
What it means. A dict key has to be hashable, and hashability requires immutability — if a key could change after insertion, the dict could never find it again. Lists are mutable, so they are unhashable by design.
The fix. Use a tuple: seen[(1, 2)] = "x". Tuples are immutable and therefore hashable, which is one of the main reasons the type exists. Lesson 7 covers this.
TypeErrorIndexing something that does not support it
Indexing something that does not support it
raises TypeError
1
2n = 42
print(n[0])
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print(n[0])
~^^^
TypeError: 'int' object is not subscriptable
What it means. Square brackets ask for an item by key or index, and a plain integer has no items.
The fix. Check what the value really is at that line — this often means a function returned a number where you expected a list. print(type(n)) settles it in one line.
AttributeErrorA method that does not exist on that type
A method that does not exist on that type
raises AttributeError
1
2text = "hello"
text.push("x")
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
text.push("x")
^^^^^^^^^
AttributeError: 'str' object has no attribute 'push'
What it means. Strings have no push; that is a JavaScript array method. And strings are immutable anyway, so no method could append to one in place.
The fix. Build a new string: text + "x". For lists the method is append, and the suggestion in the message is the fastest way to find the right name when you are close.
Values
The type was right, but that particular value was not usable.
ValueErrorText that is not a number
Text that is not a number
raises ValueError
1print(int("twelve"))
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
print(int("twelve"))
~~~^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'twelve'
What it means. The argument was the right type — int() does accept a string — but that particular string does not spell a number. Type right, value wrong, which is exactly the distinction between TypeError and ValueError.
The fix. Validate before converting, or catch it: try: int(s) / except ValueError:. Never assume text from a user or a file is numeric.
ValueErrorUnpacking the wrong number of values
Unpacking the wrong number of values
raises ValueError
1a, b = [1, 2, 3]
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
a, b = [1, 2, 3]
^^^^
ValueError: too many values to unpack (expected 2, got 3)
What it means. Unpacking requires the shape on the left to match the length on the right exactly. Two names, three values.
The fix. Match the count, or absorb the rest with a starred name: a, b, *rest = [1, 2, 3]. Lesson 7 covers unpacking properly.
Lookups
You asked a container for something it does not have.
IndexErrorAn index past the end
An index past the end
raises IndexError
1
2xs = [1, 2, 3]
print(xs[5])
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print(xs[5])
~~^^^
IndexError: list index out of range
What it means. Valid indices for a list of three are 0, 1 and 2 — and -1, -2, -3 counting from the end. Five is not among them.
The fix. Check len(xs) before indexing, or iterate with for x in xs so there is no index to get wrong. An off-by-one in a range() is the usual cause.
KeyErrorA key that is not in the dict
A key that is not in the dict
raises KeyError
1
2user = {"name": "Ada"}
print(user["email"])
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print(user["email"])
~~~~^^^^^^^^^
KeyError: 'email'
What it means. Square brackets on a dict demand the key exist. The message is the key it could not find.
The fix. Use user.get("email") for None instead of an error, user.get("email", "") for a default, or test with if "email" in user. Lesson 8 covers when each is right.
Arithmetic
The operation is defined, but not for these numbers.
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 4, in <module>
average([])
~~~~~~~^^^^
File "example.py", line 2, in average
return sum(values) / len(values)
~~~~~~~~~~~~^~~~~~~~~~~~~
ZeroDivisionError: division by zero
What it means. Written as the way this actually reaches you: not the literal 1/0 nobody writes, but an average over a list that happened to be empty. Note the two frames in the traceback — the bottom one is where it broke, the top one is the call that led there.
The fix. Guard the edge case: if not values: return 0 — or raise a clearer error of your own, which is what lesson 23 argues for.
OverflowErrorA float too large to represent
A float too large to represent
raises OverflowError
1
2
3import math
print(math.exp(10000))
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 3, in <module>
print(math.exp(10000))
~~~~~~~~^^^^^^^
OverflowError: math range error
What it means. Python integers grow without limit, but floats are IEEE-754 doubles and cap out around 1.8e308. math.exp(10000) is far past that. Worth knowing that plain 10.0 ** 10000 raises this too, while the integer 10 ** 10000 is fine.
The fix. Work in logarithms instead of raw magnitudes, which is what every library computing probabilities does for exactly this reason.
Imports
Python could not find the module, or found it and not the name you wanted from it.
ModuleNotFoundErrorA module that is not installed
A module that is not installed
raises ModuleNotFoundError
1import reqeusts
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
import reqeusts
ModuleNotFoundError: No module named 'reqeusts'
What it means. Python searched every directory on sys.path and found nothing by that name. Either it is misspelled (as here), or it is a third-party package that is not installed in the environment you are running.
The fix. Fix the spelling, or install it into the right environment. Lesson 21 covers why "the right environment" is the hard half of that sentence.
ImportErrorA name that is not in that module
A name that is not in that module
raises ImportError
1from math import sqrtt
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
from math import sqrtt
ImportError: cannot import name 'sqrtt' from 'math' (<python>/lib/python3.14/lib-dynload/math.cpython-314-darwin.so). Did you mean: 'sqrt'?
What it means. The module was found; the name inside it was not. The path in the message is the file Python actually loaded, which is useful when you suspect you are importing a different version of something than you think.
The fix. Read the suggestion, or run import math; print(dir(math)) to list what is really there.
Files and data
The outside world did not match what the code assumed: a path that is not there, bytes that are not the text you expected, JSON that is not JSON.
FileNotFoundErrorA path that is not there
A path that is not there
raises FileNotFoundError
1open("no-such-file.txt")
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
open("no-such-file.txt")
~~~~^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'no-such-file.txt'
What it means. Relative paths are resolved against the working directory of the process, not the folder the script lives in. That difference is the cause of most of these: the file is where you think, and the program is looking somewhere else.
The fix. Print Path.cwd() to see where it is really looking, and build paths from Path(__file__).parent when you mean "next to this script". Lesson 25 covers it.
IsADirectoryErrorA directory where a file was expected
A directory where a file was expected
raises IsADirectoryError
1open(".")
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 1, in <module>
open(".")
~~~~^^^^^
IsADirectoryError: [Errno 21] Is a directory: '.'
What it means. The path exists, so this is not FileNotFoundError, but it is a directory and cannot be read as a stream of bytes.
The fix. Check with Path(p).is_file() before opening, and use Path(p).iterdir() when you want to list a directory instead.
UnicodeDecodeErrorBytes that are not the text you assumed
Bytes that are not the text you assumed
raises UnicodeDecodeError
1
2data = b"\xff\xfe"
print(data.decode("utf-8"))
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
print(data.decode("utf-8"))
~~~~~~~~~~~^^^^^^^^^
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
What it means. Bytes are not text until something decodes them, and 0xff is not a legal first byte of a UTF-8 sequence. This is the error behind most "it worked on my machine" file bugs, because the default encoding has historically differed between platforms.
The fix. Name the encoding explicitly every time you open a text file — encoding="utf-8" — rather than relying on a default. Lesson 25 is built around this.
JSONDecodeErrorJSON that is not valid
JSON that is not valid
raises JSONDecodeError
1
2
3import json
json.loads("{not valid}")
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 3, in <module>
json.loads("{not valid}")
~~~~~~~~~~^^^^^^^^^^^^^^^
File "<python>/lib/python3.14/json/__init__.py", line 352, in loads
return _default_decoder.decode(s)
~~~~~~~~~~~~~~~~~~~~~~~^^^
File "<python>/lib/python3.14/json/decoder.py", line 345, in decode
obj, end = self.raw_decode(s, idx=_w(s, 0).end())
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
File "<python>/lib/python3.14/json/decoder.py", line 361, in raw_decode
obj, end = self.scan_once(s, idx)
~~~~~~~~~~~~~~^^^^^^^^
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
What it means. The message gives a line, a column and a character offset, which is the fastest way into a large document. Note the type: json.decoder.JSONDecodeError, a subclass of ValueError — so except ValueError catches it too.
The fix. Never assume a response or a file is valid JSON. Catch it, and log the first hundred characters of what you actually received. Lesson 26 covers this.
Protocols and control flow
Raised by a mechanism working as designed, not necessarily by a mistake.
StopIterationAn exhausted iterator
An exhausted iterator
raises StopIteration
1
2
3it = iter([1])
next(it)
next(it)
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 3, in <module>
next(it)
~~~~^^^^
StopIteration
What it means. Not a bug — this is the iteration protocol working. It is how an iterator says "nothing left", and it is exactly what a for loop catches invisibly on every loop it ever finishes. You only see it when calling next() yourself.
The fix. Give next a default so there is nothing to raise: next(it, None). Lesson 33 builds a for loop out of this protocol by hand.
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 4, in <module>
countdown(5)
~~~~~~~~~^^^
File "example.py", line 2, in countdown
return countdown(n - 1)
File "example.py", line 2, in countdown
return countdown(n - 1)
File "example.py", line 2, in countdown
return countdown(n - 1)
[Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded
What it means. Every call needs a case that returns without recursing, and this one has none, so it descends until CPython hits its own limit (around a thousand frames by default). Notice how the traceback collapses the repetition into a single "[Previous line repeated ...]" line rather than printing a thousand identical frames.
The fix. Add the base case: if n <= 0: return 0. If the recursion is genuinely deep and correct, rewrite it as a loop — raising the limit trades this error for a crash.
AssertionErrorAn assertion that did not hold
An assertion that did not hold
raises AssertionError
1
2total = 3
assert total == 4, "total should be four"
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 2, in <module>
assert total == 4, "total should be four"
^^^^^^^^^^
AssertionError: total should be four
What it means. An assert that fails raises this, carrying whatever message you gave it. The important caveat: assertions are removed entirely when Python runs with -O, so they are a development check, never a validation you rely on.
The fix. Keep assert for internal invariants and tests. For anything that must hold in production — user input, API responses — raise a real exception instead.
NotImplementedErrorA method a subclass was meant to override
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 5, in <module>
Shape().area()
~~~~~~~~~~~~^^
File "example.py", line 3, in area
raise NotImplementedError("subclasses must implement area()")
NotImplementedError: subclasses must implement area()
What it means. Raised on purpose, as documentation that runs: this base class is declaring that area is a subclass’s job. Not to be confused with NotImplemented, which is a value returned by comparison dunders, not an exception.
The fix. Nothing to fix here — it is behaving as designed. For a stricter version that fails at instantiation rather than at call time, use abc.ABC with @abstractmethod. Lesson 30 covers both.
RuntimeErrorA dict resized while being iterated
A dict resized while being iterated
raises RuntimeError
1
2
3
4prices = {"book": 12, "pen": 1}
for name in prices:
prices[name + " (sale)"] = 0
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 3, in <module>
for name in prices:
^^^^^^
RuntimeError: dictionary changed size during iteration
What it means. A dict refuses to be resized mid-iteration, because its internal layout may move and the loop would then be walking freed memory. Note that a list does not raise here: it silently skips elements instead, which is worse. Lesson 45 shows that happening.
The fix. Iterate over a snapshot — for name in list(prices): — or build a new dict and replace the old one.
Concurrency
Threads, processes and coroutines each have a rule you can only break once. Two of these have unusually long tracebacks because the failure happens inside the standard library, several frames below your code — reading them from the bottom up is the only practical way.
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 6, in <module>
worker.start()
~~~~~~~~~~~~^^
File "<python>/lib/python3.14/threading.py", line 989, in start
raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once
What it means. A Thread object is a one-shot handle on an operating system thread, not a reusable job description. Once it has run, that object is spent — even after join() has confirmed it finished.
The fix. Build a new Thread per run, or better, use concurrent.futures.ThreadPoolExecutor and submit the work again. Lesson 41.
SyntaxErrorawait outside a coroutine
await outside a coroutine
raises SyntaxError
1
2def load():
await fetch()
The traceback it really produced, when the build ran it
File "example.py", line 2
await fetch()
^^^^^^^^^^^^^
SyntaxError: 'await' outside async function
What it means.await is only meaningful inside an async def, so this is caught while the file is being compiled — which is why there is no traceback header above it. The commonest cause is adding an await to a function and forgetting to make it async.
The fix. Write async def load():. Every caller then has to await load() in turn, which is the change that tends to propagate upwards through a codebase.
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 9, in <module>
asyncio.run(outer())
~~~~~~~~~~~^^^^^^^^^
File "<python>/lib/python3.14/asyncio/runners.py", line 205, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "<python>/lib/python3.14/asyncio/runners.py", line 128, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "<python>/lib/python3.14/asyncio/base_events.py", line 720, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "example.py", line 7, in outer
return asyncio.run(inner())
~~~~~~~~~~~^^^^^^^^^
File "<python>/lib/python3.14/asyncio/runners.py", line 201, in run
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
RuntimeError: asyncio.run() cannot be called from a running event loop
<sys>:0: RuntimeWarning: coroutine 'inner' was never awaited
What it means.asyncio.run creates an event loop, runs one coroutine and closes the loop. There can only be one loop running on a thread, so calling it from inside a coroutine is a contradiction. Read the last line of the traceback, not the first: the frames in between are the loop machinery.
The fix. Inside a coroutine, await instead: return await inner(). Keep exactly one asyncio.run, at the top of the program.
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 8, in main
await slow()
File "example.py", line 4, in slow
await asyncio.sleep(10)
File "<python>/lib/python3.14/asyncio/tasks.py", line 705, in sleep
return await future
^^^^^^^^^^^^
asyncio.exceptions.CancelledError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "example.py", line 10, in <module>
asyncio.run(main())
~~~~~~~~~~~^^^^^^^^
File "<python>/lib/python3.14/asyncio/runners.py", line 205, in run
return runner.run(main)
~~~~~~~~~~^^^^^^
File "<python>/lib/python3.14/asyncio/runners.py", line 128, in run
return self._loop.run_until_complete(task)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
File "<python>/lib/python3.14/asyncio/base_events.py", line 720, in run_until_complete
return future.result()
~~~~~~~~~~~~~^^
File "example.py", line 7, in main
async with asyncio.timeout(0.05):
~~~~~~~~~~~~~~~^^^^^^
File "<python>/lib/python3.14/asyncio/timeouts.py", line 115, in __aexit__
raise TimeoutError from exc_val
TimeoutError
What it means. The asyncio.timeout block cancelled what it was waiting on and then raised. Since 3.11 this is the builtin TimeoutError, not asyncio.TimeoutError — they are the same object now, and older code catching the asyncio one still works.
The fix. Catch it: except TimeoutError:. Anything inside the block receives CancelledError first, so use try/finally there to release resources.
PicklingErrorA lambda sent to a process pool
A lambda sent to a process pool
raises PicklingError
1
2
3
4
5import multiprocessing as mp
if __name__ == "__main__":
with mp.Pool(2) as pool:
pool.map(lambda n: n * 2, [1, 2, 3])
The traceback it really produced, when the build ran it
AttributeError: module '__main__' has no attribute '<lambda>'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "example.py", line 5, in <module>
pool.map(lambda n: n * 2, [1, 2, 3])
~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<python>/lib/python3.14/multiprocessing/pool.py", line 367, in map
return self._map_async(func, iterable, mapstar, chunksize).get()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "<python>/lib/python3.14/multiprocessing/pool.py", line 774, in get
raise self._value
File "<python>/lib/python3.14/multiprocessing/pool.py", line 540, in _handle_tasks
put(task)
~~~^^^^^^
File "<python>/lib/python3.14/multiprocessing/connection.py", line 216, in send
self._send_bytes(_ForkingPickler.dumps(obj))
~~~~~~~~~~~~~~~~~~~~~^^^^^
File "<python>/lib/python3.14/multiprocessing/reduction.py", line 51, in dumps
cls(buf, protocol).dump(obj)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^
_pickle.PicklingError: Can't pickle <function <lambda> at 0x10b8a1220>: it's not found as __main__.<lambda>
when serializing tuple item 0
when serializing tuple item 0
when serializing tuple item 3
What it means. Arguments to a worker process are serialised with pickle, and pickling a function stores only its name so the child can look it up again. A lambda has no name to look up. The same applies to a function defined inside another function, and to anything holding an open file or a socket.
The fix. Use a module-level function, or functools.partial of one. Lesson 42 runs both the failure and the fix.
The deep end
Errors from the machinery under classes. You will not meet these while learning the language, and you will meet them the first time you reach for one of its extension points.
AttributeErrorAn attribute a __slots__ class did not declare
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 8, in <module>
p.z = 3
^^^
AttributeError: 'Point' object has no attribute 'z' and no __dict__ for setting new attributes
What it means.__slots__ replaces the per-instance dictionary with a fixed set of slots, so there is nowhere to put an attribute that was not declared. The message says so explicitly — "and no __dict__ for setting new attributes" — which distinguishes it from an ordinary typo.
The fix. Add the name to __slots__, or drop __slots__ if the class needs to be open. Lesson 45 measures what it actually saves before you pay this price.
TypeErrorTwo metaclasses in one class
Two metaclasses in one class
raises TypeError
1
2
3
4
5
6
7class Serialisable(type): pass
class Observable(type): pass
class Model(metaclass=Serialisable): pass
class Widget(metaclass=Observable): pass
class Form(Model, Widget): pass
The traceback it really produced, when the build ran it
Traceback (most recent call last):
File "example.py", line 7, in <module>
class Form(Model, Widget): pass
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
What it means. A class has exactly one metaclass, and it must be a subclass of every base’s metaclass. Two unrelated ones cannot be reconciled, so this is also the reason two libraries that each use a metaclass cannot be mixed.
The fix. Write a third metaclass inheriting from both, if you control them. Better: do not use one — __init_subclass__ and class decorators compose, and lesson 48 shows the same jobs done with them.
How to read any of them. Start at the bottom: the last line is the
exception type and the message, and that is the actual problem. Then read the frame directly
above it, which is where it happened. Only then walk further up, which tells you how the
program got there. Beginners tend to read a traceback top-down and give up in library code;
the useful information is at the bottom, and since 3.11 the ~~~^^^ markers point
at the precise sub-expression that failed rather than just the line.
Newsletter
New writing, when it lands.
Occasional notes on shipping software with AI agents. No schedule, no filler, unsubscribe in one click.