AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

File Handling in Python: Reading and Writing Data

📚 Programming & Coding⏱️ 22 min read🎓 Grade 10
✍️ AI Computer Institute Editorial Team Published: March 2026 CBSE-aligned · Peer-reviewed · 22 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

File Handling in Python: Reading and Writing Data

Imagine you write a small Python program to record the scores of every over in a gully cricket match. You type in runs after runs, the program keeps a neat running total in a variable, and at the end it prints "Final Score: 142/6". You feel proud, close the program window — and the moment you open it again the next day, the scoreboard is gone. Every run, every wicket, vanished. If you have ever built a program like this, you have already discovered the single biggest limitation of ordinary variables: they live only as long as the program is running. The instant the program ends, Python hands that memory back to the operating system, and whatever was stored inside disappears. This chapter is about the fix for that problem — teaching your program to write information onto the computer's permanent storage, and to read it back later, using Python's file handling tools.

Why Variables Are Not Enough: RAM Versus Disk

To understand why files matter, it helps to be precise about where a Python variable actually lives. When your program runs, Python stores every variable in RAM (Random Access Memory) — a workspace that is extremely fast but also temporary. Think of RAM as a classroom blackboard: the teacher can write on it instantly and erase it instantly, which makes it perfect for working out a problem, but the moment the period ends and the duster is used, everything on it is gone. A file, on the other hand, lives on the disk (an SSD or hard drive) — the equivalent of a notebook. Writing in a notebook is a bit slower than scribbling on a blackboard, but whatever you write stays there even after you shut the notebook, walk home, and open it again the next morning.

This is exactly the distinction your cricket scoreboard program was missing. The variable total_runs was blackboard information. To make the scoreboard survive between sessions — the way the IRCTC website remembers your PNR days after you booked a ticket, or the way a UPI app keeps a record of a payment you made last week — the data must be pushed out of RAM and onto disk, inside a file. Python gives you exactly one gateway for doing this: the built-in open() function.

Opening a File: The open() Function and Its Modes

Every file operation in Python begins the same way — you ask Python to open a "connection" between your program and a specific file on disk. This connection is called a file object, and it is what you will read from or write through.

file = open("diary.txt", "w")

The first argument, "diary.txt", is the name of the file you want to work with. The second argument, "w", is the mode — it tells Python what you intend to do with the file, and this single letter changes the behaviour dramatically. The three modes you must know cold at this stage are:

  • "r" (read mode) — opens an existing file so you can read its contents. If the file does not exist, Python refuses and raises an error rather than silently creating one. This is the default mode if you don't specify anything.
  • "w" (write mode) — opens a file so you can write to it. If the file does not exist, Python creates it. But if the file already exists, Python immediately erases everything inside it the moment you open it in this mode — even before you write a single character. This detail causes more bugs among beginners than almost anything else in file handling, and we will come back to it with a worked demonstration shortly.
  • "a" (append mode) — also opens a file for writing, and creates it if it doesn't exist, but instead of erasing existing content, it moves to the end of the file so anything you write is added after what is already there.

There is a fourth mode, "r+", which allows both reading and writing on an existing file without erasing it, but it is used rarely at this stage — CBSE's introductory treatment of file handling focuses on r, w, and a, and so will we.

Writing Data to a File

Once a file is open in write mode, you push text into it using the write() method. Let's build the diary example properly.

file = open("diary.txt", "w")
file.write("Dear Diary, today I learned file handling in Python.\n")
file.close()

Trace through exactly what happens. Line 1 creates diary.txt (or empties it if it already existed) and hands back a file object stored in the variable file. Line 2 sends one line of text into that file — notice the \n at the end. This is deliberate and important: unlike Python's print() function, write() does not automatically add a newline after the text you give it. If you want your next write to start on a fresh line, you must type \n yourself. Line 3, file.close(), tells Python you are finished — this is not a formality. Until a file is closed, Python may hold your written data in a temporary buffer in RAM rather than physically pushing it to disk, purely for speed. If your program crashed before close() ran, some or all of your "written" text could be lost. Closing forces Python to flush that buffer to disk and release the file so other programs can use it.

Reading Data Back

Now that diary.txt exists on disk, we can open it again in a completely new program run — proving that the data survived — and read it using mode "r".

file = open("diary.txt", "r")
content = file.read()
print(content)
file.close()

Here, read() pulls the entire contents of the file into a single string, which we store in content. Running this prints:

Dear Diary, today I learned file handling in Python.

followed by one blank line, because the string itself already ends in \n and print() adds a second newline of its own. This small detail — that the newline character you wrote is part of the data, not decoration — matters a great deal once you start processing file contents line by line, which we'll do next.

The File Pointer: How Python Tracks Position While Reading

When you open a file, Python maintains an invisible marker called the file pointer (or cursor) that tracks exactly where in the file your next read or write will begin. Understanding this pointer is the key to understanding every reading method Python offers.

Suppose marks.txt contains two lines:

Aditi,88
Rohan,76

and we read it using readline(), which returns just one line at a time and then leaves the pointer sitting right after that line:

file = open("marks.txt", "r")
first_line = file.readline()
second_line = file.readline()
print(first_line)
print(second_line)
file.close()

Before any reading happens, the pointer sits at the very beginning of the file, right before the "A" of "Aditi". The first call to readline() reads forward until it hits the newline character, returns "Aditi,88\n", and leaves the pointer sitting right at the start of "Rohan". The second call repeats the process from that new position and returns "Rohan,76\n". If you called readline() a third time, it would return an empty string "", because the pointer is already at the end of the file — there is nothing left to read. The diagram below shows these three pointer positions side by side.

The File Pointer While Reading marks.txt Before any readline() Aditi,88 Rohan,76 pointer at position 0 readline() After first readline() Aditi,88 Rohan,76 returned "Aditi,88\n" readline() After second readline() Aditi,88 Rohan,76 returned "Rohan,76\n" A third readline() call now would return "" (empty string) — the pointer has reached the end of the file. This is exactly how a for-loop over a file object reads it: one line, one pointer step, at a time.

Three Ways to Read a File

Python gives you three related but distinct tools for reading, and choosing the right one depends on what you plan to do with the data afterward.

  • read() pulls in the entire file as one single string, newlines and all. Good when you want to treat the whole file as one block of text, or when the file is small enough that loading it all into memory at once is not a problem.
  • readline() pulls in just the next line, including its trailing \n, and moves the pointer forward by exactly that much. Good when you need fine control over reading one line, checking something about it, and then deciding whether to read the next.
  • readlines() pulls in every remaining line and hands them back as a list of strings, each one still ending in \n. So for our two-line file, readlines() would return ["Aditi,88\n", "Rohan,76\n"].

In real programs, the most common pattern by far is neither of these directly — it's looping over the file object itself, which Python lets you do exactly like looping over a list:

with open("marks.txt", "r") as file:
    for line in file:
        print(line.strip())

Here line.strip() removes the trailing \n (and any stray spaces) before printing, so the output is clean:

Aditi,88
Rohan,76

This for-loop is doing exactly what the pointer diagram above showed — one readline()-like step per iteration — until the pointer reaches the end, at which point the loop simply stops.

The with Statement: A Safer Way to Handle Files

Notice that the example above used with open(...) as file: instead of a bare open() followed later by close(). This is the pattern professional Python code — and increasingly, CBSE's own model answers — prefers, and there is a solid reason: if an error occurs somewhere between opening and closing a file, a plain close() call written at the bottom of your code might never actually run, silently leaving the file open and any buffered data unflushed. The with statement guarantees the file will be closed automatically the instant the indented block underneath it finishes — whether it finished normally or crashed halfway through. You should think of every earlier example in this chapter (the diary, the two-line marks file) as a simplified version for teaching the mechanics; from now on, always reach for with in real code:

with open("diary.txt", "w") as file:
    file.write("Dear Diary, today I learned file handling in Python.\n")

There is no file.close() line here at all, and none is needed — the moment Python's execution steps out of the indented block, the file is closed for you.

A Common Trap: Write Mode Erases Everything

Now let's deliberately walk into the mistake mentioned earlier, because seeing it happen is far more convincing than being told about it. Suppose your diary already contains one line from the example above. You now want to add a second thought, but you accidentally reopen the file in "w" mode instead of "a" mode:

with open("diary.txt", "w") as file:
    file.write("I also learned about the with statement.\n")

If you now read diary.txt, you will find only one line: "I also learned about the with statement." The earlier line about learning file handling is gone — not appended to, not merged, simply erased, because "w" mode truncates (empties) the file the instant it is opened, regardless of whether you go on to write anything at all. This is precisely why append mode exists.

Appending Instead of Overwriting

with open("diary.txt", "a") as file:
    file.write("Today the class average in the CS test was 85.33.\n")

Because the mode here is "a", Python moves the pointer to the end of the existing file before writing, so this new line is added after whatever was already there — nothing is lost. This is the mode you want any time you are logging events over time, such as recording each transaction in a UPI payment history file, or appending each new match score to a season-long cricket log, one line per match, without disturbing the matches already recorded.

Two More Misconceptions, Corrected

Misconception: "write() and writelines() add newlines automatically, the way print() does." They do not. If you pass a list of strings to writelines() without \n at the end of each one, every line will be glued together with no separation at all:

lines = ["Monday: Present", "Tuesday: Present", "Wednesday: Absent"]
with open("attendance.txt", "w") as file:
    file.writelines(lines)

This produces one unbroken line inside attendance.txt: Monday: PresentTuesday: PresentWednesday: Absent. To get three separate lines, each string in the list must end with \n itself:

lines = ["Monday: Present\n", "Tuesday: Present\n", "Wednesday: Absent\n"]
with open("attendance.txt", "w") as file:
    file.writelines(lines)

Misconception: "Data is safely on disk the instant write() is called." In reality, Python (and the operating system beneath it) typically buffers written data in memory for efficiency, and only guarantees it is physically pushed to disk when the file is closed — which is exactly why the with statement and proper closing are not optional politeness, but a correctness requirement.

Worked Example: Building a Class Marks Register

Let's now combine writing and reading into one complete, realistic task: storing a small class's marks on disk, then reading them back to compute the class average — something a CBSE student's own result-management mini-project might genuinely need to do.

students = [("Aditi", 88), ("Rohan", 76), ("Simran", 92)]

with open("marks.txt", "w") as file:
    for name, score in students:
        file.write(name + "," + str(score) + "\n")

This writes three lines into marks.txt: Aditi,88, Rohan,76, and Simran,92, each ending in \n. Now, in what could be an entirely separate program run, we read that file back and compute the average:

total = 0
count = 0

with open("marks.txt", "r") as file:
    for line in file:
        name, score = line.strip().split(",")
        total = total + int(score)
        count = count + 1

average = total / count
print("Class average:", round(average, 2))

Trace it carefully. On the first pass through the loop, line is "Aditi,88\n"; strip() removes the newline, giving "Aditi,88"; split(",") breaks it at the comma into the list ["Aditi", "88"], which is unpacked into name = "Aditi" and score = "88". Since score is still text at this point (everything read from a file is text, even if it looks like a number), we must convert it using int(score) before adding it to total. After all three lines, total is 88 + 76 + 92 = 256 and count is 3, so average is 256 / 3, which Python's division rounds to 85.33 for display. The final output is:

Class average: 85.33

Notice the two ideas working together here: writing turned Python values into text on disk, and reading had to turn that text back into numbers before any arithmetic could happen. This "everything from a file is text until you convert it" rule is one of the most important habits to build early, because forgetting the int() conversion and trying to add scores directly would either silently glue the text together with + or crash with a TypeError the moment you mixed a string and a number.

Handling a File That Does Not Exist

What happens if your program tries to open a file for reading before that file has ever been created — say, a student's hall ticket file that generates only after the CBSE registration process completes? Python does not return an empty result; it raises an error and stops the program:

with open("hall_ticket.txt", "r") as file:
    print(file.read())

If hall_ticket.txt does not exist, this raises FileNotFoundError and halts execution. In real programs you rarely want a crash to be the user's experience, so you catch the error and respond gracefully:

try:
    with open("hall_ticket.txt", "r") as file:
        print(file.read())
except FileNotFoundError:
    print("Hall ticket not generated yet. Please check again later.")

The try block attempts the risky operation; if FileNotFoundError occurs anywhere inside it, control jumps straight to the matching except block instead of crashing the program, and execution continues normally afterward.

Where Does the File Actually Go? A Note on Paths

When you write open("marks.txt", "w"), you are giving Python a relative path — Python creates or looks for marks.txt inside whatever folder your program itself is currently running from. This is usually what you want for school projects. If you instead need to point to a file somewhere specific on your computer, you use an absolute path, such as "C:\\Users\\Aditi\\Documents\\marks.txt" on Windows or "/home/aditi/marks.txt" on Linux. Notice the doubled backslash in the Windows example — a single backslash inside a normal Python string is treated as the start of a special escape sequence (the same family as the \n you've been using all chapter), so a literal backslash must be written twice, or the whole path written as a raw string like r"C:\Users\Aditi\Documents\marks.txt", where the r tells Python to treat every backslash literally.

Practice: Test Your Understanding

  1. You open a file with open("scores.txt", "w") that already contains ten lines of past scores. Before you write a single character, how many lines remain in the file, and why?
  2. A file log.txt contains three lines. You call readline() three times in a row and then call it a fourth time. What does the fourth call return, and what does that tell you about the file pointer?
  3. Predict the exact contents of a file after this code runs, and explain why the loop needs \n inside it rather than just once at the end: for i in range(1, 4): file.write(str(i)).
  4. Why does professional Python code almost always prefer with open(...) as file: over calling open() and close() separately? Name the specific failure mode that with prevents.
  5. A classmate reads marks from a file and writes total = total + score without first converting score with int(). What actually goes wrong?

Summary

Ordinary Python variables live in RAM and disappear the instant a program ends; files live on disk and persist across program runs, which is why any data that needs to survive — a cricket scoreboard, a UPI transaction history, a class marks register — must be written to a file rather than merely held in a variable. The open() function is the single gateway to every file operation, and the mode you choose — "r" to read an existing file, "w" to write while erasing any existing content, or "a" to write while preserving existing content — changes its behaviour completely; opening in "w" mode truncates a file the instant it is opened, even before you write anything. Every open file maintains an invisible file pointer that tracks your current reading position; read() pulls in the whole file at once, readline() pulls in one line and advances the pointer past it, readlines() returns every remaining line as a list, and looping directly over a file object reads it one line at a time until the pointer reaches the end. Text written to or read from a file is always a string, even if it represents a number, so numeric data must be explicitly converted with functions like int() before arithmetic and explicitly converted back with str() before writing. Finally, the with statement should be your default way of opening files, since it closes the file automatically — flushing any buffered data to disk — even if an error interrupts your program, while attempting to read a file that was never created should be wrapped in a try/except FileNotFoundError block so your program fails gracefully instead of crashing.

← Python Dictionaries and Sets: Organizing Data SmartlyPython Data Classes: Cleaner Data Structures →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn