# File Handling

Course: [Python](https://www.devtutorials.ai/learn/python) · Section: File Operations
Source: https://www.devtutorials.ai/learn/python/file-operations/file-handling

> Opening, the nine modes, three ways to read, why closing matters, the with statement, file errors, and text, CSV and JSON files.

Everything you have written so far has disappeared.

Every mark, every name, every list — gone the moment the program ended. A variable lives in memory, and memory is cleared when the program stops.

A school cannot work that way. The marks have to still be there tomorrow morning. That means writing them somewhere that outlives the program: a **file**.

Here is that idea in action — a file that keeps its contents after the program has ended:

```python runnable title="Something that survives the program"
with open("fh_first.txt", "w") as sheet:
    sheet.write("Asha,78\n")
    sheet.write("Ravi,85\n")

with open("fh_first.txt") as sheet:
    print(sheet.read())
```

A file is storage that outlives your program. Python opens one, hands you an object to read from or write to, and the file must be closed when you are done — which is why `with` exists.

## What You Will Learn

In this lesson, you will learn:

- What a file object is, and why opening one is not the same as reading it
- All nine opening modes, and the one difference that matters most: `w` destroys, `a` adds
- Three ways to read, and when each is the right one
- Why an unclosed file can lose data you thought you had written
- The `with` statement, and why nothing else in this lesson matters as much
- How to handle a file that is missing, locked or not what you expected
- Text, CSV and JSON — the three file shapes you will actually meet

By the end of this lesson, you will be able to open files in each mode, read them back three ways, write text safely, explain why closing matters, use `with` so closing happens on every path, handle missing files, and choose between plain text, CSV and JSON.

Every cell on this page writes its own file before reading it, so you can run any cell on its own and get the same answer. The files live in the browser tab, not on your computer, and they disappear when you reload the page.

## Introduction to File Handling

### What File Handling Is

**File handling** is the set of things a program does with files: open, read, write, close. Python builds all of it on one function, `open()`, and one kind of object, the **file object**.

### File Operations in Python

There are four, and they always happen in this order:

1. **Open** the file, saying what you intend to do with it.
2. **Read** from it, or **write** to it.
3. **Close** it.

Step 3 is the one people forget, and it is the one that loses data.

### Working with Files

Here are the four steps, written out longhand:

```python runnable title="The four steps, written out the long way"
sheet = open("fh_steps.txt", "w")
sheet.write("Asha,78\n")
sheet.close()

sheet = open("fh_steps.txt", "r")
print(sheet.read())
sheet.close()
```

That works, and you will see it in older code. Later on this page you will replace all six lines with four better ones.

### File Objects

`open()` does not give you the contents of the file. It gives you a **file object**: a thing that knows which file it is attached to, what you are allowed to do with it, and **where you have got to** in it.

```python runnable title="A file object is not the text"
sheet = open("fh_object.txt", "w")
print(sheet)
print(type(sheet).__name__)
sheet.close()
```

That position marker is the part beginners miss. Reading a file moves it to the end, and a second read from the same object gives you nothing.

```python runnable title="The second read comes back empty"
with open("fh_twice.txt", "w") as sheet:
    sheet.write("Asha,78\n")

with open("fh_twice.txt") as sheet:
    print("First read:", repr(sheet.read()))
    print("Second read:", repr(sheet.read()))
```

Nothing is wrong with the file. The marker is simply sitting at the end.

## Opening Files

### Using the open() Function

The function takes a file name and, optionally, a mode:

```text
file_object = open(filename, mode)
```

`filename` is the name of the file. `mode` says what you intend to do. If you leave the mode out, you get `"r"` — read.

```python runnable title="The mode is optional and defaults to read"
with open("fh_default.txt", "w") as sheet:
    sheet.write("Asha,78\n")

with open("fh_default.txt") as sheet:
    print(sheet.read().strip())
```

### File Opening Modes

There are nine modes. The table is the reference; the two that matter most are `w` and `a`:

| Mode | Meaning | If the file is missing | If it exists |
|---|---|---|---|
| `r` | read | `FileNotFoundError` | read from the start |
| `w` | write | created | **emptied first** |
| `a` | append | created | added to at the end |
| `x` | exclusive create | created | `FileExistsError` |
| `r+` | read and write | `FileNotFoundError` | kept, marker at the start |
| `w+` | write and read | created | **emptied first** |
| `a+` | append and read | created | kept, writes go to the end |

Add `b` for binary or `t` for text to any of them. `t` is the default, so `"r"` and `"rt"` are the same thing.

`w` empties the file the instant it is opened — before you write a single character. If you meant to add to a file and typed `w`, the old contents are gone, and there is no undo. When in doubt, use `a`.

### File Properties

A file object will tell you about itself.

```python runnable title="name, mode and closed"
sheet = open("fh_props.txt", "w")
print("name:", sheet.name)
print("mode:", sheet.mode)
print("closed:", sheet.closed)

sheet.write("Asha,78\n")
sheet.close()

print("closed after close():", sheet.closed)
```

`closed` is the useful one: it is how you check whether a file still needs closing.

## File Modes

Now each mode in turn, with the ones you will actually use first.

### Read Mode (`r`)

Opens an existing file for reading, with the marker at the start. The file must already exist.

```python runnable title="Reading a file that is there"
with open("fh_read.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\n")

with open("fh_read.txt", "r") as sheet:
    print(sheet.read())
```

If the file is not there, the result is an exception:

```python runnable expect-error title="Reading a file that is not there"
with open("fh_nothing_here.txt", "r") as sheet:
    print(sheet.read())
```

### Write Mode (`w`)

Creates the file if it is missing, and **empties it** if it is not.

```python runnable title="w empties the file first"
with open("fh_write.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\nMeera,60\n")

with open("fh_write.txt") as sheet:
    print("Before:", repr(sheet.read()))

with open("fh_write.txt", "w") as sheet:
    sheet.write("Dev,70\n")

with open("fh_write.txt") as sheet:
    print("After: ", repr(sheet.read()))
```

Three marks went in and one came out. That is `w` doing exactly what it says.

### Append Mode (`a`)

Creates the file if it is missing, and adds to the **end** if it is not. Nothing already there is touched.

```python runnable title="a adds to the end"
with open("fh_append.txt", "w") as sheet:
    sheet.write("Asha,78\n")

with open("fh_append.txt", "a") as sheet:
    sheet.write("Ravi,85\n")

with open("fh_append.txt", "a") as sheet:
    sheet.write("Meera,60\n")

with open("fh_append.txt") as sheet:
    print(sheet.read())
```

This is the mode for a log, an attendance register, or anything that only ever grows.

### Exclusive Create Mode (`x`)

Creates the file, and refuses if it already exists. Use it when overwriting would be a disaster.

```python runnable title="x refuses to overwrite"
import os

if os.path.exists("fh_exclusive.txt"):
    os.remove("fh_exclusive.txt")

with open("fh_exclusive.txt", "x") as sheet:
    sheet.write("Asha,78\n")
print("Created")

try:
    with open("fh_exclusive.txt", "x") as sheet:
        sheet.write("Ravi,85\n")
except FileExistsError as error:
    print("FileExistsError — refused to overwrite", error.filename)
```

The first three lines delete the file if it is there, so that you can press Run again and see the same thing.

### Binary Mode (`b`)

Text mode gives you `str` and handles the encoding for you. Binary mode gives you `bytes` — the raw contents, untouched. Images, sound and compressed files are all binary.

```python runnable title="bytes, not text"
with open("fh_binary.bin", "wb") as data:
    data.write(b"Asha 78")

with open("fh_binary.bin", "rb") as data:
    raw = data.read()

print(type(raw).__name__, raw)
print(raw.decode("utf-8"))
```

Note the `b` before the quotes: `b"Asha 78"` is a bytes literal, not text.

### Text Mode (`t`)

The default. Python decodes the bytes into text for you, and converts line endings so that `\n` works the same on every operating system. You almost never type `t`, because leaving it out means the same thing.

### Read and Write Mode (`r+`)

Opens an existing file for both, with the marker at the start. Nothing is emptied.

```python runnable title="r+ keeps what is there"
with open("fh_rplus.txt", "w") as sheet:
    sheet.write("Asha,78\n")

with open("fh_rplus.txt", "r+") as sheet:
    print("Read first:", repr(sheet.read()))
    sheet.write("Ravi,85\n")

with open("fh_rplus.txt") as sheet:
    print("Now holds: ", repr(sheet.read()))
```

The write landed at the end because reading had already moved the marker there. Had you written first, you would have overwritten the start.

### Write and Read Mode (`w+`)

Both, but it empties the file on open. So the first read gives you nothing — there is nothing left to read.

```python runnable title="w+ empties first, then lets you read"
with open("fh_wplus.txt", "w") as sheet:
    sheet.write("old marks\n")

with open("fh_wplus.txt", "w+") as sheet:
    print("Read straight after opening:", repr(sheet.read()))
    sheet.write("Asha,78\n")
    sheet.seek(0)
    print("After seek(0):", repr(sheet.read()))
```

`seek(0)` moves the marker back to the start. Without it, the read after a write returns nothing.

### Append and Read Mode (`a+`)

Both, nothing is emptied, and **every write goes to the end** no matter where the marker is.

```python runnable title="a+ always writes at the end"
with open("fh_aplus.txt", "w") as sheet:
    sheet.write("Asha,78\n")

with open("fh_aplus.txt", "a+") as sheet:
    sheet.write("Ravi,85\n")
    sheet.seek(0)
    print(sheet.read())
```

## Reading Files

The modes are done. Now the three ways to actually get text out of a file — and the fourth way, the one to prefer.

### Reading the Whole File with `read()`

`read()` returns the entire file as one string.

```python runnable title="read gives you one string"
with open("fh_whole.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\nMeera,60\n")

with open("fh_whole.txt") as sheet:
    contents = sheet.read()

print(repr(contents))
print(len(contents), "characters")
```

Good for a small file. A large one goes entirely into memory, so this is not the tool for a file bigger than your computer is comfortable with.

### Reading One Line with `readline()`

`readline()` returns the next line, including its `\n`, and moves the marker past it.

```python runnable title="readline, one call at a time"
with open("fh_lines.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\nMeera,60\n")

with open("fh_lines.txt") as sheet:
    print(repr(sheet.readline()))
    print(repr(sheet.readline()))
    print(repr(sheet.readline()))
    print(repr(sheet.readline()))
```

The last call returns an empty string. That is how `readline()` says there is nothing left — not `None`, and not an exception.

### Reading All Lines with `readlines()`

`readlines()` returns a list, one item per line, each still carrying its `\n`.

```python runnable title="readlines gives you a list"
with open("fh_all.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\nMeera,60\n")

with open("fh_all.txt") as sheet:
    rows = sheet.readlines()

print(rows)
print(len(rows), "rows")

for row in rows:
    name, mark = row.strip().split(",")
    print(name, "scored", mark)
```

### Looping Over the File Itself

This is the one you should reach for. A file object can be looped over directly, and it reads one line at a time instead of loading everything.

```python runnable title="The usual way to read a file"
with open("fh_loop.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\nMeera,60\n")

total = 0
with open("fh_loop.txt") as sheet:
    for row in sheet:
        name, mark = row.strip().split(",")
        total += int(mark)
        print(f"{name:<8}{mark:>4}")

print("Total:", total)
```

Every line you read keeps its `\n`. `.strip()` removes it, along with any stray spaces. Forgetting it is why a printed name sometimes has a blank line under it, and why `int(mark)` sometimes works and sometimes does not.

## Writing Files

Reading has three routes; writing is simpler — but the newline catches everyone.

### Writing with `write()`

`write()` writes exactly the text you give it — and **no newline**. If you want lines, you add `\n` yourself.

```python runnable title="write adds nothing of its own"
with open("fh_nonewline.txt", "w") as sheet:
    sheet.write("Asha,78")
    sheet.write("Ravi,85")

with open("fh_nonewline.txt") as sheet:
    print(repr(sheet.read()))
```

Two writes, one line. That is almost never what you wanted.

```python runnable title="With the newlines put back"
with open("fh_newline.txt", "w") as sheet:
    sheet.write("Asha,78\n")
    sheet.write("Ravi,85\n")

with open("fh_newline.txt") as sheet:
    print(sheet.read())
```

`write()` also returns the number of characters it wrote, which is occasionally useful and often surprising.

```python runnable title="write returns a count"
with open("fh_count.txt", "w") as sheet:
    written = sheet.write("Asha,78\n")

print("characters written:", written)
```

### Writing Several Lines with `writelines()`

`writelines()` takes a list of strings and writes them one after another. Despite the name, **it does not add newlines either**.

```python runnable title="writelines, with and without newlines"
rows = ["Asha,78", "Ravi,85", "Meera,60"]

with open("fh_wl_bad.txt", "w") as sheet:
    sheet.writelines(rows)

with open("fh_wl_bad.txt") as sheet:
    print("Without newlines:", repr(sheet.read()))

with open("fh_wl_good.txt", "w") as sheet:
    sheet.writelines(row + "\n" for row in rows)

with open("fh_wl_good.txt") as sheet:
    print("With newlines:")
    print(sheet.read())
```

### Overwriting against Appending

The whole difference, side by side.

```python runnable title="w against a, from the same starting point"
for mode in ["w", "a"]:
    with open("fh_compare.txt", "w") as sheet:
        sheet.write("Asha,78\n")

    with open("fh_compare.txt", mode) as sheet:
        sheet.write("Ravi,85\n")

    with open("fh_compare.txt") as sheet:
        print(f"mode {mode!r} leaves:", repr(sheet.read()))
```

## Closing Files

You have been using `with` since the first example. Here is what it saves you from.

### Using `close()`

```python runnable title="close, and what happens after it"
sheet = open("fh_close.txt", "w")
sheet.write("Asha,78\n")
print("closed?", sheet.closed)

sheet.close()
print("closed?", sheet.closed)

try:
    sheet.write("Ravi,85\n")
except ValueError as error:
    print("ValueError:", error)
```

### Why Closing Files Matters

Three reasons, and the first one is the one that bites.

1. **Your writing may not have happened yet.** Python holds recent writes in a buffer and sends them to the file in batches. `close()` flushes that buffer. A program that ends badly without closing can leave the file short.
2. **An open file holds a resource.** An operating system allows a limited number of open files at once, and a loop that opens thousands without closing them will eventually fail.
3. **Other programs may be locked out.** On some systems a file that one program holds open cannot be opened by another.

You can watch reason 1 happen with your own eyes:

```python runnable title="The contents only exist once it is closed"
sheet = open("fh_flush.txt", "w")
sheet.write("Asha,78\n")

with open("fh_flush.txt") as check:
    print("While still open:", repr(check.read()))

sheet.close()

with open("fh_flush.txt") as check:
    print("After close():   ", repr(check.read()))
```

Run that and look at the first line. The text you "wrote" was not in the file yet.

`close()` written at the end of a block is not enough. If any line above it raises, the `close()` is skipped and the file stays open with its buffer unflushed. This is the problem `with` exists to solve, and it is why you should not write `open()` and `close()` by hand.

## Context Managers

### The `with` Statement

A **context manager** is an object that knows how to tidy up after itself. A file is one. `with` asks it to do that tidying automatically, at the end of the block — whichever way the block ends.

```text
with open(filename, mode) as file_object:
    work with file_object
```

```python runnable title="with closes the file for you"
with open("fh_with.txt", "w") as sheet:
    sheet.write("Asha,78\n")
    print("inside the block, closed?", sheet.closed)

print("after the block, closed?", sheet.closed)
```

Nobody called `close()`. It happened anyway.

### Automatic Closing, Even When Things Go Wrong

This is the part that matters. The file is closed even if the block raises.

```python runnable title="Closed even on the way out of an exception"
try:
    with open("fh_safe.txt", "w") as sheet:
        sheet.write("Asha,78\n")
        raise ValueError("something went wrong halfway through")
except ValueError as error:
    print("Caught:", error)

print("closed?", sheet.closed)

with open("fh_safe.txt") as sheet:
    print("and the write survived:", repr(sheet.read()))
```

The exception escaped, the file was closed on the way out, and the buffer was flushed. Hand-written `close()` would have been skipped.

### Benefits of Context Managers

- You cannot forget to close the file, because you never wrote the close.
- The file is closed on every path out — normal end, `return`, or exception.
- The block makes it obvious how long the file is open for.
- Several files can be opened in one `with`, separated by commas.

```python runnable title="Two files in one with"
with open("fh_src.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\n")

with open("fh_src.txt") as source, open("fh_dest.txt", "w") as target:
    for row in source:
        target.write(row.upper())

with open("fh_dest.txt") as target:
    print(target.read())
```

## Exception Handling in File Operations

### Handling File Errors

Four things go wrong with files, and each has its own exception.

```python runnable title="Four file failures, each named"
jobs = ["missing", "readonly", "wrongmode", "closed"]

with open("fh_errors.txt", "w") as sheet:
    sheet.write("Asha,78\n")

for job in jobs:
    try:
        if job == "missing":
            open("fh_not_a_real_file.txt")
        elif job == "readonly":
            with open("fh_errors.txt", "w") as sheet:
                sheet.read()
        elif job == "wrongmode":
            with open("fh_errors.txt", "r") as sheet:
                sheet.write("Ravi,85\n")
        elif job == "closed":
            sheet = open("fh_errors.txt")
            sheet.close()
            sheet.read()
    except Exception as error:
        print(type(error).__name__ + ":", error)
```

`UnsupportedOperation` is a kind of `ValueError`, so `except ValueError` catches those middle two.

### try-except-finally

The long form, written out so you can see what `with` is doing for you.

```python runnable title="The same job without with"
sheet = None
try:
    sheet = open("fh_not_a_real_file.txt")
    print(sheet.read())
except FileNotFoundError as error:
    print("Could not find", error.filename)
finally:
    if sheet is not None:
        sheet.close()
    print("finished either way")
```

Note `sheet = None` at the top and the `if` inside `finally`. Without them, a failed `open()` leaves `sheet` unset and the `finally` block raises a second exception on top of the first.

### Safe File Operations

`with` and `try` together: `with` guarantees the close, `try` decides what happens when the file is not there.

```python runnable title="The shape to use in real code"
def read_marks(filename):
    try:
        with open(filename) as sheet:
            return [row.strip() for row in sheet if row.strip()]
    except FileNotFoundError:
        print(f"  ({filename} is missing — treating it as empty)")
        return []

with open("fh_marks.txt", "w") as sheet:
    sheet.write("Asha,78\nRavi,85\n")

print(read_marks("fh_marks.txt"))
print(read_marks("fh_no_such_sheet.txt"))
```

Do not check `os.path.exists()` and then open the file. Between the two lines the file can be deleted or renamed, and your careful check reports a file that is gone by the time you open it. Open it inside a `try` and handle `FileNotFoundError` — that covers both cases with one piece of code.

## Working with File Types

Everything so far has been plain text. Real programs meet three shapes of file, and each has its own way back into values.

### Text Files (`.txt`)

Plain text is the simplest case, and everything above has been working with it. The only decision is how you separate the parts of a row.

```python runnable title="A text file read back into values"
with open("fh_plain.txt", "w") as sheet:
    sheet.write("Asha,Maths,78\n")
    sheet.write("Ravi,Science,85\n")

with open("fh_plain.txt") as sheet:
    for row in sheet:
        name, subject, mark = row.strip().split(",")
        print(f"{name} scored {mark} in {subject}")
```

That works until a name contains a comma. Which is why CSV exists.

### CSV Files (`.csv`)

A **CSV** file is a table: one row per line, values separated by commas. The `csv` module handles the awkward cases — commas inside a value, quotes, blank fields — that splitting by hand gets wrong.

```python runnable title="Writing a CSV"
import csv

with open("fh_marks.csv", "w", newline="") as sheet:
    writer = csv.writer(sheet)
    writer.writerow(["name", "subject", "mark"])
    writer.writerows([
        ["Asha", "Maths", 78],
        ["Ravi", "Science", 85],
        ["Meera", "English", 60],
    ])

with open("fh_marks.csv") as sheet:
    print(sheet.read())
```

`newline=""` is not optional. Without it, the `csv` module and the file object both add line endings, and the file comes out double-spaced on some systems.

```python runnable title="Reading a CSV row by row"
import csv

with open("fh_marks.csv", "w", newline="") as sheet:
    writer = csv.writer(sheet)
    writer.writerow(["name", "subject", "mark"])
    writer.writerows([
        ["Asha", "Maths", 78],
        ["Ravi", "Science", 85],
        ["Meera", "English", 60],
    ])

with open("fh_marks.csv", newline="") as sheet:
    for row in csv.reader(sheet):
        print(row)
```

Every value comes back as text, including the marks. `78` written is `"78"` read.

```python runnable title="DictReader names the columns for you"
import csv

with open("fh_marks.csv", "w", newline="") as sheet:
    writer = csv.writer(sheet)
    writer.writerow(["name", "subject", "mark"])
    writer.writerows([
        ["Asha", "Maths", 78],
        ["Ravi", "Science", 85],
        ["Meera", "English", 60],
    ])

total = 0
with open("fh_marks.csv", newline="") as sheet:
    for row in csv.DictReader(sheet):
        total += int(row["mark"])
        print(row["name"], "->", row["mark"])

print("Total:", total)
```

`DictReader` uses the first line as the column names, so you write `row["mark"]` instead of `row[2]` — and the code survives someone adding a column.

```python runnable title="DictWriter writes dictionaries back out"
import csv

records = [
    {"name": "Asha", "mark": 78},
    {"name": "Ravi", "mark": 85},
]

with open("fh_dict.csv", "w", newline="") as sheet:
    writer = csv.DictWriter(sheet, fieldnames=["name", "mark"])
    writer.writeheader()
    writer.writerows(records)

with open("fh_dict.csv") as sheet:
    print(sheet.read())
```

### JSON Files (`.json`)

**JSON** is the format for nested data — a dictionary inside a dictionary inside a list. A CSV is flat; JSON is not.

Four function names, and the rule is simple: the ones ending in `s` work with **strings**, the ones without work with **files**.

| Function | Direction | Works with |
|---|---|---|
| `json.dump()` | Python → file | a file |
| `json.load()` | file → Python | a file |
| `json.dumps()` | Python → text | a string |
| `json.loads()` | text → Python | a string |

```python runnable title="Writing JSON to a file"
import json

record = {
    "name": "Asha",
    "class": "7A",
    "marks": {"Maths": 78, "Science": 85},
}

with open("fh_record.json", "w") as sheet:
    json.dump(record, sheet, indent=2)

with open("fh_record.json") as sheet:
    print(sheet.read())
```

`indent=2` is what makes it readable. Leave it out and the whole thing is one long line.

```python runnable title="Reading JSON back"
import json

record = {
    "name": "Asha",
    "class": "7A",
    "marks": {"Maths": 78, "Science": 85},
}

with open("fh_record.json", "w") as sheet:
    json.dump(record, sheet, indent=2)

with open("fh_record.json") as sheet:
    loaded = json.load(sheet)

print(type(loaded).__name__)
print(loaded["name"], "is in", loaded["class"])
print("Maths:", loaded["marks"]["Maths"])
```

It comes back as a real dictionary, with the numbers still numbers. That is the advantage over CSV, where everything arrives as text.

```python runnable title="The string versions"
import json

record = {"name": "Asha", "mark": 78}

as_text = json.dumps(record)
print(as_text)
print(type(as_text).__name__)

back = json.loads(as_text)
print(back["mark"] + 1)
```

One last thing worth knowing — what happens when the JSON is not valid:

```python runnable title="When the file is not valid JSON"
import json

with open("fh_broken.json", "w") as sheet:
    sheet.write("{name: Asha}")

try:
    with open("fh_broken.json") as sheet:
        json.load(sheet)
except json.JSONDecodeError as error:
    print("JSONDecodeError on line", error.lineno)
    print("Problem:", error.msg)

print("It is a kind of ValueError:", issubclass(json.JSONDecodeError, ValueError))
```

JSON needs double quotes around every key and every piece of text. Single quotes are valid Python and invalid JSON, which is the single most common cause of that error.

Choose by shape, not by habit. A flat table of rows and columns is a CSV, and a spreadsheet will open it. Anything nested — a record containing a list, or a dictionary of dictionaries — is JSON. Plain text is for notes and logs, where nothing needs reading back as data.

## Common mistakes

Before moving on, here are some of the mistakes beginners make with files.

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| `open(name, "w")` meaning to add | The old contents are gone | `w` empties the file on open | Use `a` |
| Reading a file twice from one object | The second read is `''` | The marker is at the end | `seek(0)`, or open it again |
| No `close()` and no `with` | The file is short or empty | The buffer was never flushed | Use `with` |
| `close()` after a line that raises | The file stays open | The `close()` line is skipped | Use `with` |
| `write()` without `\n` | Everything on one line | `write()` adds nothing of its own | Add `"\n"` yourself |
| `writelines(rows)` expecting lines | Everything on one line | It does not add newlines either | `writelines(r + "\n" for r in rows)` |
| Forgetting `.strip()` on a read line | A blank line between rows, or `ValueError` from `int()` | Each line still carries its `\n` | `row.strip()` |
| `csv.writer` without `newline=""` | A blank line between every row | Two layers both add line endings | `open(name, "w", newline="")` |
| Expecting numbers back from a CSV | `TypeError` when you add them | Every CSV value reads as text | `int(row["mark"])` |
| Single quotes in a JSON file | `JSONDecodeError` | JSON only allows double quotes | Write it with `json.dump()` |
| `os.path.exists()` then `open()` | A `FileNotFoundError` anyway | The file can vanish between the two lines | Open inside a `try` |
| Reading a huge file with `read()` | The program slows or stops | The whole file goes into memory | Loop over the file object |

</div>

## What to Remember

- A file is storage that outlives the program. `open()` gives you a **file object**, not the contents.
- A file object remembers **where you have got to**. A second `read()` gives you nothing until you `seek(0)`.
- **`w` empties the file on open; `a` adds to the end.** Getting this wrong destroys data with no undo.
- `x` refuses to overwrite. `b` gives you bytes; `t` is text and is the default.
- `read()` gives one string, `readline()` gives the next line, `readlines()` gives a list — and looping over the file object is usually better than all three.
- Every line you read keeps its `\n`. Use `.strip()`.
- Neither `write()` nor `writelines()` adds a newline. You add it.
- Closing flushes the buffer. **Until a file is closed, what you wrote may not be in it.**
- **Use `with`.** It closes the file on every path out, including through an exception, and it is the reason you never need `close()`.
- Put the `try` around the `open()` and the `with` inside it. Catch `FileNotFoundError` by name.
- CSV is a flat table and reads back as text. `newline=""` when writing, `DictReader` when reading.
- JSON is for nested data and keeps types. `dump`/`load` for files, `dumps`/`loads` for strings.

Next: **Advanced Functions**
