Skip to content
VLSI Mentor

Python Part I · Module 14 · Errors and Exception Handling

The Errors Beginners Actually Hit

There are dozens of built-in exception types and you will meet about six of them. What makes them worth learning is not the names — the names are on the last line of the traceback and you can read — but the fact that each one is a diagnosis. A KeyError is not merely a missing key; it is evidence that the data did not have the shape your code assumed, which is a different bug from the one a ValueError indicates, which is different again from a TypeError even though the two are constantly confused. This chapter is the six types, each as a program that genuinely fails, and for each one the inference you are entitled to draw the moment you see it.

Foundation17 min readPythonPart IFundamentalsErrorsDebugging

Part I · Module 14 · Chapter 14.3 · The Errors Beginners Actually Hit

1. An Error Type Is a Diagnosis

The last line of a traceback has two halves, and both carry information:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
ValueError: invalid literal for int() with base 10: 'n/a'

The message tells you about this specific failure: the offending text was 'n/a'. The type tells you about the category of mistake, and that is the half people skim. Three lines from the canonical examples:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int("n/a")          # ValueError  - right type, impossible content
result["runtime"]   # KeyError    - the dict did not have the shape you assumed
tests[9]            # IndexError  - the sequence was shorter than you thought

Each comment is a hypothesis about what you did wrong, available before you have looked at a single line of your own code. That is what makes the six worth knowing.

Every example below is a real program that really fails. No exception is caught anywhere in this chapter — catching is Chapter 14.4 — so each one is also more practice at reading a traceback bottom-up.

2. NameError — the name does not exist here

Azvya Education Pvt. Ltd.VLSI Mentor
nameerror.py (BROKEN ON PURPOSE)
runtime_s = 182.4
print(f"runtime: {runtime_S}")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "nameerror.py", line 2, in <module>
    print(f"runtime: {runtime_S}")
                      ^^^^^^^^^
NameError: name 'runtime_S' is not defined

The diagnosis: you used a name that was never bound, or bound somewhere this code cannot see. In practice it is one of four things:

  1. a typo or a case difference (runtime_S for runtime_s — Python is case-sensitive, as Chapter 2.2 noted);
  2. a name defined inside a function and used outside it (Chapter 11.10);
  3. a module you forgot to import (Chapter 13.2);
  4. a name defined later in the file than the line that uses it.

When the name is close to one Python knows, you get a suggestion for free:

Azvya Education Pvt. Ltd.VLSI Mentor
suggestion.py (BROKEN ON PURPOSE)
tests = ["smoke", "cdc"]
print(lenn(tests))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "suggestion.py", line 2, in <module>
    print(lenn(tests))
          ^^^^
NameError: name 'lenn' is not defined. Did you mean: 'len'?

3. TypeError — the operation does not apply to that type

Azvya Education Pvt. Ltd.VLSI Mentor
typeerror.py (BROKEN ON PURPOSE)
runtime = 182.4
print("runtime: " + runtime)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "typeerror.py", line 2, in <module>
    print("runtime: " + runtime)
          ~~~~~~~~~~~~^~~~~~~~~
TypeError: can only concatenate str (not "float") to str

The diagnosis: the types themselves are incompatible. Not the values — no float whatsoever could be concatenated to a string. The fix is a conversion or an f-string (Chapter 4.12), which is why f"runtime: {runtime}" is the idiomatic form.

The most common TypeError in real scripts is subtler and comes from a function that returned nothing:

Azvya Education Pvt. Ltd.VLSI Mentor
nonetype.py (BROKEN ON PURPOSE)
def total_runtime(values):
    """Forgot to return — so it returns None."""
    sum(values)


t = total_runtime([182.4, 204.1])
print(t + 10)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "nonetype.py", line 7, in <module>
    print(t + 10)
          ~~^~~~
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'

Any error mentioning NoneType should send you looking for a function that forgot to return, which is exactly the trap Chapter 11.8 was about. The failing line is line 7; the bug is on line 3.

4. ValueError — right type, impossible content

This is the one that gets confused with TypeError, and the distinction is clean:

Azvya Education Pvt. Ltd.VLSI Mentor
valueerror.py (BROKEN ON PURPOSE)
print(int("182"))
print(int("n/a"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
182
Traceback (most recent call last):
  File "valueerror.py", line 2, in <module>
    print(int("n/a"))
          ~~~^^^^^^^
ValueError: invalid literal for int() with base 10: 'n/a'

int accepts strings — line 1 proves it. So the type was fine; this particular string could not be interpreted as a number. That is a ValueError.

Compare directly:

Azvya Education Pvt. Ltd.VLSI Mentor
both.py (BROKEN ON PURPOSE)
print(int("n/a"))
print(int(["182"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "both.py", line 1, in <module>
    print(int("n/a"))
          ~~~^^^^^^^
ValueError: invalid literal for int() with base 10: 'n/a'

Only the first failure appears, because the program stops there. Swap the lines and you get the other:

Azvya Education Pvt. Ltd.VLSI Mentor
typefirst.py (BROKEN ON PURPOSE)
print(int(["182"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "typefirst.py", line 1, in <module>
    print(int(["182"]))
          ~~~^^^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'list'

TypeError means "I do not accept that kind of thing at all." ValueError means "I accept that kind of thing, but not that one."

The diagnosis for ValueError: your data is dirtier than your code assumes. It is the signature error of parsing anything a tool produced — a dash where a number should be, an empty field, a N/A.

Another common form has nothing to do with parsing:

Azvya Education Pvt. Ltd.VLSI Mentor
unpack.py (BROKEN ON PURPOSE)
row = "smoke PASS 182.4".split()
name, status = row
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "unpack.py", line 2, in <module>
    name, status = row
    ^^^^^^^^^^^^
ValueError: too many values to unpack (expected 2)

Three fields, two names. Chapter 11.9 used this shape deliberately; here it is as a failure.

5. IndexError — the sequence was shorter than you thought

Azvya Education Pvt. Ltd.VLSI Mentor
indexerror.py (BROKEN ON PURPOSE)
tests = ["smoke", "cdc"]
print(tests[0])
print(tests[1])
print(tests[2])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke
cdc
Traceback (most recent call last):
  File "indexerror.py", line 4, in <module>
    print(tests[2])
          ~~~~~^^^
IndexError: list index out of range

The diagnosis: an off-by-one, or data that was shorter than expected. The two cases look identical and the fix differs completely — one is a loop bound, the other is a missing input line — so the next question is always how long is it actually?

The specific real-world form is a line with fewer fields than the format promised:

Azvya Education Pvt. Ltd.VLSI Mentor
shortrow.py (BROKEN ON PURPOSE)
for line in ["smoke  PASS  182.4", "reset  FAIL"]:
    fields = line.split()
    print(fields[0], fields[2])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
smoke 182.4
Traceback (most recent call last):
  File "shortrow.py", line 3, in <module>
    print(fields[0], fields[2])
                     ~~~~~~^^^
IndexError: list index out of range

Note that IndexError says nothing about which index — the message is always list index out of range. The caret line is what tells you it was fields[2].

6. KeyError — the dictionary did not have that key

Azvya Education Pvt. Ltd.VLSI Mentor
keyerror.py (BROKEN ON PURPOSE)
result = {"test": "smoke", "status": "PASS", "runtime_s": 182.4}
print(result["runtime"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "keyerror.py", line 2, in <module>
    print(result["runtime"])
          ~~~~~~^^^^^^^^^^^
KeyError: 'runtime'

The message is just the key — no explanation, because none is needed.

The diagnosis: the data did not have the shape your code assumed. Either the key is spelled differently (runtime_s, as it is here), or the dictionary came from somewhere that omits it for some records. The second case is the dangerous one: it works for thousands of records and fails on one.

Chapter 8.3 gave the two tools for this — in and .get() — and they remain the right answer whenever a key is genuinely optional:

Azvya Education Pvt. Ltd.VLSI Mentor
optional.py
result = {"test": "smoke", "status": "PASS"}
print(result.get("runtime_s", "not recorded"))
print("runtime_s" in result)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
not recorded
False

7. FileNotFoundError — the path is not there

Azvya Education Pvt. Ltd.VLSI Mentor
missing.py (BROKEN ON PURPOSE)
f = open("sim.log")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "missing.py", line 1, in <module>
    f = open("sim.log")
FileNotFoundError: [Errno 2] No such file or directory: 'sim.log'

Files are Module 15's subject and open is explained properly there; it appears here only because it is the thing that raises this error.

The diagnosis: the path is wrong, or you are not where you think you are. The second is far more common than people expect — a relative path like sim.log is resolved from the working directory, not from the script's directory, exactly as Chapter 13.5 Section 6 found for imports. The same script run from two places looks for two different files.

Note also [Errno 2]: the operating system reported this, not Python.

8. The Two That Are Not About Your Data

ModuleNotFoundError is a NameError for files:

Azvya Education Pvt. Ltd.VLSI Mentor
nomodule.py (BROKEN ON PURPOSE)
import panadas
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "nomodule.py", line 1, in <module>
    import panadas
ModuleNotFoundError: No module named 'panadas'

The diagnosis: a typo, something not installed, or a path problem — in that order of likelihood. Chapter 13.1 covered where Python looks.

AttributeError means you asked an object for something it does not have:

Azvya Education Pvt. Ltd.VLSI Mentor
attrerror.py (BROKEN ON PURPOSE)
name = "smoke"
name.push("_test")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "attrerror.py", line 2, in <module>
    name.push("_test")
    ^^^^^^^^^
AttributeError: 'str' object has no attribute 'push'

The message names the type first, and that is the useful half — 'str' object tells you what Python thinks it is holding, which is often the surprise. The overwhelmingly common version in real scripts:

Azvya Education Pvt. Ltd.VLSI Mentor
nonattr.py (BROKEN ON PURPOSE)
def find_status(lines):
    """Returns None when nothing matches — and nothing matches here."""
    for line in lines:
        if line.startswith("STATUS"):
            return line


status = find_status(["UVM_INFO ...", "UVM_INFO ..."])
print(status.strip())
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "nonattr.py", line 9, in <module>
    print(status.strip())
          ^^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'strip'

'NoneType' object has no attribute is one of the most common errors in all of Python, and it always means the same thing: something returned None and you used the result as though it had not. A function with no matching case, a .get() that missed, or a function that forgot to return.

9. Predict, Run, Observe

Four statements. Name the error type for each before reading on:

Azvya Education Pvt. Ltd.VLSI Mentor
predict.py (BROKEN ON PURPOSE)
data = {"test": "smoke", "runtime_s": "182.4"}

print("A:", data["runtime_s"] + 10)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "predict.py", line 3, in <module>
    print("A:", data["runtime_s"] + 10)
                ~~~~~~~~~~~~~~~~~~^~~~
TypeError: can only concatenate str (not "int") to str

A is a TypeError, not a ValueError. The dictionary stored the runtime as a string — note the quotes in the literal. Adding an int to a str is a type mismatch; no values of those two types would work.

Azvya Education Pvt. Ltd.VLSI Mentor
predict2.py (BROKEN ON PURPOSE)
data = {"test": "smoke", "runtime_s": "n/a"}

print("B:", float(data["runtime_s"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
Traceback (most recent call last):
  File "predict2.py", line 3, in <module>
    print("B:", float(data["runtime_s"]))
                ~~~~~^^^^^^^^^^^^^^^^^^^
ValueError: could not convert string to float: 'n/a'

B is a ValueError. float accepts strings; it does not accept this string. The same expression with "182.4" works, which is the definition of a value problem rather than a type problem.

Notice the two messages are different in shape as well: int() says invalid literal for int() with base 10, float() says could not convert string to float. Both are ValueError.

10. The Card

TypeMeansLook for
NameErrorthe name is not bound heretypo, case, scope, missing import
TypeErrorthe operation does not apply to that typea None from a function that forgot to return; a str where a number was meant
ValueErrorright type, impossible contentdirty input — N/A, empty field, a dash
IndexErrorthe sequence is shorter than assumedoff-by-one, or a short line
KeyErrorthe dict lacks that keyspelling, or a record of a different shape
FileNotFoundErrorthe path is not therewrong path, or wrong working directory
AttributeErrorthe object has no such nameNoneType means something returned None
ModuleNotFoundErrorthe import target is not findabletypo, not installed, path

Two shortcuts worth memorising outright:

  • NoneType anywhere → find the function that did not return.
  • TypeError versus ValueError → ask whether any value of that type would have worked. If yes, it is a ValueError.

11. Exercises

Exercise 1 — produce all six. Write six one-or-two-line files, each raising a different type from the card. Keep them; they are a useful reference.

Exercise 2 — the distinction. Write two calls to int(), one raising TypeError and one raising ValueError. State the rule that separates them in your own words.

Exercise 3 — the None hunt. Write a function that returns None on one input, then use its result two different ways so that one produces a TypeError and the other an AttributeError. Say what both have in common.

Exercise 4 — short line. Parse a list of log lines where one has fewer fields than the rest. Record the error and the caret line, and say which index failed.

Exercise 5 — the optional key. Take the KeyError example and fix it twice: once with in, once with .get(). Then say when fixing it that way is the wrong thing to do.

Exercise 6 — working directory. Produce a FileNotFoundError with a relative path, then make the identical script succeed by running it from elsewhere. Explain in one sentence what that proves about relative paths.

Exercise 7 — read the type, not the message. For each, name the type without running it: (a) "5" * "5" (b) int("5.5") (c) [1,2][2] (d) {"a":1}["b"] (e) None.strip() (f) improt json.

Exercise 8 — diagnose from the type alone. A colleague says only "it threw a KeyError on the nightly run but works on my machine". Write the three questions you would ask, each justified by what KeyError licenses you to infer.

12. Summary

  • An error type is a diagnosis, not a label — it narrows the cause before you read any of your own code.
  • NameError — the name is not bound where you used it: typo, case, scope, or missing import.
  • TypeError — the operation does not apply to that type at all.
  • ValueError — the type was right and the content was not; the signature error of parsing real tool output.
  • The test that separates them: would any value of that type have worked? If yes, it is a ValueError.
  • IndexError — the sequence was shorter than assumed; the message never says which index, so read the caret line.
  • KeyError — the data did not have the shape your code assumed; the message is just the key.
  • FileNotFoundError — wrong path, or a relative path resolved from a working directory you did not expect.
  • AttributeError mentioning NoneType is one of the most common errors in Python, and always means something returned None.
  • Suppressing a KeyError with .get() hides a question when the key was supposed to be there.

You can now classify a failure and find it. The rest of the module is about deciding what your script should do about it — starting with the observation that the right answer is often nothing at all.

Where this fits

Part of the Python curriculum.