Cover image for Python Exceptions and File Handling: Code That Survives Bad Input

At a glance

Reading time

~200 words/min

Published

8 hours ago

Jun 13, 2026

Views

3

All-time total

Python Exceptions and File Handling: Code That Survives Bad Input

Everything you have built so far assumed a friendly world: numbers arrive as numbers, files exist, users type what they are told. The real world is not friendly. Users type "twenty" into age fields, files vanish, networks drop, and disks fill. This lesson teaches the two skills that separate scripts from software: handling errors deliberately with exceptions, and working with files, which is both the most common real-world task in Python and the richest source of things that go wrong. The two topics share a lesson because they meet constantly in practice.

A mindset shift before the syntax. Errors in Python are not failures of character; they are values, with types and messages, flowing through a second channel of your program. You have been reading them since Part 1, every TypeError and NameError the playground showed you. Today you learn to catch them, choose which ones to catch, raise your own, and guarantee cleanup happens no matter what. Programs that handle errors well do not avoid exceptions; they have opinions about them.

What you will learn in Part 7

  • Reading tracebacks from the bottom up, like a professional
  • try and except, and why you always catch specific exceptions
  • else, finally, and raising exceptions of your own
  • Opening, reading, and writing text files
  • The with statement: cleanup you cannot forget
  • The line-by-line processing pattern behind real data work

Note

Before you start

You need functions from Part 4 and the data structures from Part 5. The playground runs file examples too: it has its own private file system in your browser, so you can create and read files with zero risk to your machine.

1. Reading a traceback without panic

When an unhandled exception occurs, Python prints a traceback, and learning to read it is worth more than any single language feature. Read it bottom-up: the last line names the exception type and message, the line above shows exactly where it happened, and the lines above that trace the chain of calls that led there, your Part 4 functions calling functions. The deepest frame is where it broke; the frames above are how you got there. Nothing in the traceback is filler, and nothing is shaming you; it is a map drawn at the moment of failure.

def parse_age(text):
    return int(text)

def greet(text):
    age = parse_age(text)
    print(f"You are {age}")

greet("twenty")

# Traceback (most recent call last):
#   ...
#   line 5, in greet  ->  age = parse_age(text)
#   line 2, in parse_age  ->  return int(text)
# ValueError: invalid literal for int() with base 10: 'twenty'

2. try and except: choosing your failures

The try statement marks a block where you expect trouble; except names the exception type you are prepared to handle and provides the response. When an exception of that type occurs anywhere in the try block, execution jumps straight to the matching except, and the program continues afterward instead of dying. The cardinal rule, the one that separates careful code from careless: catch the most specific exception you can. A bare except, or except Exception, swallows everything including bugs you needed to see, typos, broken logic, the works, and turns loud failures into silent corruption.

raw_values = ["42", "seven", "19", "", "85"]

valid = []
for raw in raw_values:
    try:
        valid.append(int(raw))
    except ValueError:
        print(f"skipping bad value: {raw!r}")

print(valid)            # [42, 19, 85]
print(sum(valid))       # 146

You can stack multiple except clauses for different responses to different failures, and capture the exception object with as to inspect its message. The else clause runs only when no exception occurred, keeping the success path visually separate, and finally runs no matter what, success, failure, even a return, which makes it the home of cleanup. Here is the full anatomy once, so you recognize every part in the wild.

def read_config_value(d, key):
    try:
        raw = d[key]
        value = float(raw)
    except KeyError:
        print(f"missing setting: {key}")
        return None
    except (TypeError, ValueError) as err:
        print(f"bad setting {key}: {err}")
        return None
    else:
        print(f"{key} loaded fine")
        return value
    finally:
        print("lookup attempted")     # always runs

cfg = {"timeout": "2.5", "retries": "three"}
read_config_value(cfg, "timeout")    # loaded fine
read_config_value(cfg, "retries")    # bad setting
read_config_value(cfg, "debug")      # missing setting

Exception types form a family tree, and knowing its shape makes your except clauses smarter. KeyError and IndexError are both children of LookupError, so catching LookupError handles a missing dict key and a bad list position in one clause; ValueError and TypeError sit elsewhere on the tree; and almost everything descends from Exception, which is why catching Exception is nearly as indiscriminate as a bare except. The practical rule: catch the most specific type that covers the failures you genuinely expect, and let everything else escape loudly, because an error you did not anticipate is information you need, not noise to suppress.

Where you catch matters as much as what. A function deep in your program usually should not catch anything; it should do its job and let failures travel upward, because only the caller knows whether a missing file means crash, retry, or use defaults. The natural home for try blocks is the boundary: where your program meets user input, files, or the network. Inner code raises, boundary code decides. Programs structured that way have a few thoughtful excepts at the edges instead of paranoid wrapping everywhere, and they are dramatically easier to debug.

Checkpoint

Why is a bare "except:" considered harmful?

3. Raising your own exceptions

Handling errors is half the story; signaling them is the other half. The raise statement throws an exception on purpose, and well-designed functions use it to refuse bad input at the boundary instead of producing nonsense later. You can raise built-in types like ValueError with a clear message, and when your project grows, you can define custom exception classes, one line of inheritance from Part 6, so callers can catch your errors specifically. This is the polite contract of professional code: fail fast, fail loudly, fail with a message that names the problem.

class InsufficientFunds(Exception):
    """Raised when a withdrawal exceeds the balance."""

def withdraw(balance, amount):
    if amount <= 0:
        raise ValueError(f"amount must be positive, got {amount}")
    if amount > balance:
        raise InsufficientFunds(f"need {amount}, have {balance}")
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFunds as err:
    print(f"Declined: {err}")        # Declined: need 250, have 100

4. Files: reading and writing text

Now the second half of the lesson, and the reason the first half matters so much. The open function connects your program to a file; the mode string says what you intend: "r" to read, "w" to write a fresh file, destroying any existing content, and "a" to append to the end. Files must be closed when you finish, and because forgetting is human, Python provides the with statement: it opens the file, binds it to a name, and guarantees closing when the block ends, even if an exception explodes in the middle. There is exactly one acceptable way to work with files in modern Python, and this is it.

# Writing: "w" creates or overwrites
with open("scores.txt", "w") as f:
    f.write("Amina,87\n")
    f.write("Zane,91\n")
    f.write("Ruwan,78\n")

# Reading the whole file
with open("scores.txt") as f:        # mode "r" is the default
    content = f.read()
print(content)

# Appending adds to the end
with open("scores.txt", "a") as f:
    f.write("Neha,84\n")

For real data work you rarely want the whole file as one string; you want it line by line, and Python makes the file object itself iterable, a fact that will feel deeply natural after Part 9. The pattern below, open, loop, strip, split, convert, accumulate, is the skeleton of half the scripts ever written: log analyzers, gradebooks, data cleaners, import jobs. Every piece of it is a skill from an earlier lesson, which is exactly the point of a syllabus.

total = 0
count = 0
with open("scores.txt") as f:
    for line in f:                     # one line at a time, memory-friendly
        line = line.strip()            # remove the trailing newline
        if not line:                   # skip blanks, truthiness from Part 2
            continue
        name, score = line.split(",")  # unpacking from Part 5
        total += int(score)
        count += 1

print(f"average of {count} scores: {total / count:.1f}")

Two practical details save real-world pain. Text files have an encoding, the mapping between bytes on disk and characters in your program, and the safe modern habit is to say it explicitly: open("notes.txt", encoding="utf-8"). Leaving it out makes Python guess from system settings, and a script that worked on your machine then mangles accented names on someone else's. And when a file might legitimately not exist yet, the choice from section 2 applies: either check first with the pathlib tools you will meet in Part 8, or simply try the open and catch FileNotFoundError, which is the more Pythonic instinct.

Files bring exceptions home: open raises FileNotFoundError when the path does not exist, PermissionError when the system refuses, and your parsing can raise ValueError on a malformed line. Wrap the risky parts in the try machinery from sections 2 and 3 and you have the complete, professional shape of file-processing code. One more upgrade arrives in Part 8, the pathlib module, which handles file paths across operating systems; today's open calls are the foundation it builds on.

Checkpoint

What does the with statement guarantee when opening a file?

5. Practice: a survivor-grade gradebook

The playground below combines everything: it writes a deliberately messy data file, blank lines, a corrupt row, a non-numeric score, then processes it without crashing, collecting good rows and reporting bad ones. This is not a toy; it is the exact shape of production data ingestion, scaled down. The exercises harden it further. The playground gives you a private in-browser file system, so experiment freely.

Python playground

Exercise 3 hides this lesson's deepest idea: the same except clause handles both the failures Python generates and the ones you raise yourself, because exceptions are one unified channel. Once that clicks, error handling stops being defensive boilerplate and becomes part of how you design: functions promise results or raise, callers decide what survival means, and nothing fails silently.

! Common mistakes to avoid

  • Wrapping fifty lines in one giant try with a bare except.

    Keep try blocks small, around the one or two lines that can actually fail, and name the exception. Big try blocks hide which operation failed and catch bugs you needed to see.

  • Opening files without with, then leaking file handles when an error skips the close().

    Always use with open(...) as f. It is shorter, and the close is guaranteed on every exit path, including exceptions.

  • Opening in "w" mode to add data, and wiping the file.

    "w" truncates immediately on open. Appending is "a". The data lost to this mistake worldwide is beyond measure; mode strings deserve two seconds of attention.

  • Catching an exception and doing nothing: except ValueError: pass.

    Silent passes erase evidence. At minimum log or count the failure, like the bad list in the playground; future you, debugging at midnight, needs that trail.

? Frequently asked questions

Should I check conditions first or just try and catch? +

Python culture favors EAFP: easier to ask forgiveness than permission. Attempt the operation and handle the exception, rather than pre-checking everything. Pre-checks race against reality; the file can vanish between your check and your open.

Do exceptions make Python slow? +

The try block itself costs almost nothing; raising and catching costs a little, and it does not matter for anything except the hottest inner loops. Correctness and clarity pay better than micro-optimizing error paths.

What about reading huge files? +

The line-by-line loop already handles them: it holds one line in memory at a time, so a ten-gigabyte log streams happily. Part 9 explains the iterator machinery that makes this work.

How do I handle binary files like images? +

Open with "rb" or "wb" and you get bytes instead of text. For real formats, use a library: Pillow for images, for example. Text files cover the overwhelming majority of beginner-to-intermediate needs.

6. Recap and what comes next

Your code can now survive reality: you read tracebacks bottom-up, catch specific exceptions in small try blocks, use else and finally with intent, raise your own errors including custom classes, and process files with with, line by line, without losing data or leaking handles. The combination, files plus exceptions, is the skeleton of nearly every practical script you will ever write.

Next the course hands you the keys to the warehouse: Part 8, modules, packages, and a standard library tour, where imports finally make sense and Python's batteries-included reputation gets demonstrated module by module. The Exception Handling and File Handling lessons in the Learn Python app below carry matching quizzes, and the full syllabus is on the series hub.

💡

Pro tip

When debugging file code, print(repr(line)) instead of print(line). repr shows the invisible characters, the trailing newline, the stray spaces, the empty string, that are almost always the actual culprit.

Learn Python Android app icon

Practice on the go

Learn Python, the free Android app

Every topic in this series lives in the app too: bite-size lessons, runnable examples, quizzes, mini projects, and an offline Python playground that runs on your phone.

Newsletter

Want more posts like this?

Get practical software notes and tutorials delivered when something new is published.

No spam. Unsubscribe anytime.

How did this land?

Comments

0
Log in or sign up to join the discussion and react to this post.

No comments yet. Be the first to share your thoughts.

Related posts

Important functionalities of Pandas in Python : Tricks and Features

Pandas is one of my favorite libraries in python. It’s very useful to visualize the data in a clean structural manner. Nowadays Pandas is widely used in Data Science, Machine Learning and other areas.

5 years ago

How to get data from twitter using Tweepy in Python?

To start working on Python you need to have Python installed on your PC. If you haven’t installed python. Go to the Python website and get it installed.

6 years ago

Predicting per capita income of the US using linear regression

Python enables us to predict and analyze any given data using Linear regression. Linear Regression is one of the basic machine learning or statistical techniques created to solve complex problems.

6 years ago

Essential Sorting Algorithms for Computer Science Students

Algorithms are commonly taught in Computer Science, Software Engineering subjects at your Bachelors or Masters. Some find it difficult to understand due to memorizing.

6 years ago

GraphQL in Laravel Using Lighthouse

In modern web development, GraphQL has emerged as a powerful alternative to REST APIs due to its flexibility and efficiency.

1 year ago