Files, errors & OOP basics

Real scripts read and write files, survive bad input, and group related data and behaviour into classes. This lesson covers all three so you can finish a small tool end to end.

Files

pathlib handles small files in one line each:

from pathlib import Path

Path("notes.txt").write_text("BeeProton rocks\n", encoding="utf-8")
for line in Path("notes.txt").read_text(encoding="utf-8").splitlines():
    print(line)

For larger files, open them with with, which closes the file even if an error occurs:

with open("sales.txt", "a", encoding="utf-8") as f:      # append mode
    f.write("2026-09-23,Pune,18400\n")

with open("sales.txt", encoding="utf-8") as f:
    for line in f:                        # streams one line at a time
        date, city, amount = line.strip().split(",")
        print(city, int(amount))
Mode Meaning
"r" read (default), error if the file is missing
"w" write, truncates an existing file
"a" append to the end
"x" create, error if it already exists
"rb" / "wb" binary, for images and PDFs

Always pass encoding="utf-8" so Hindi or Tamil text survives the round trip.

Exceptions

try:
    value = int(input("Enter a number: "))
except ValueError:
    print("That wasn't a number")
else:
    print("You entered", value)
finally:
    print("Done")
  • try holds the risky code.
  • except runs for a matching error type.
  • else runs only when nothing failed.
  • finally always runs — use it for cleanup.

Catch specific exceptions (ValueError, FileNotFoundError, ZeroDivisionError, KeyError) rather than a bare except:, which also swallows Ctrl+C. Raise your own when the data is wrong:

def apply_discount(amount, percent):
    if not 0 <= percent <= 100:
        raise ValueError(f"percent must be 0-100, got {percent}")
    return round(amount * (1 - percent / 100), 2)

Classes

A class describes what an object knows (attributes) and what it can do (methods).

class Invoice:
    def __init__(self, number, amount):
        self.number = number
        self.amount = amount

    def with_gst(self, rate=18):
        return round(self.amount * (1 + rate / 100), 2)

    def __repr__(self):
        return f"Invoice({self.number!r}, {self.amount})"

inv = Invoice("BP-101", 5000)
print(inv.with_gst())    # 5900.0
print(inv)               # Invoice('BP-101', 5000)

__init__ runs when the object is created; self is the object itself and must be the first parameter of every instance method. __repr__ controls how the object prints, which makes debugging far easier.

Inheritance lets a specialised class reuse a general one:

class ExportInvoice(Invoice):
    def with_gst(self, rate=0):       # exports are zero-rated
        return float(self.amount)

For plain data holders, @dataclasses.dataclass writes __init__ and __repr__ for you.

Common mistakes

  • Forgetting self in a method definition or when assigning an attribute.
  • Leaving files open instead of using with.
  • Using "w" when you meant "a" and wiping yesterday's data.
  • Writing except Exception: pass, which hides the very bug you needed to see.

Practice

Write a Student class with name, marks (a list) and the methods average() and grade(). Read ten students from a comma-separated text file inside a try / except FileNotFoundError, skip malformed lines with a warning, and write a report file listing each student's name, average and grade.

Next: move on to NumPy and Pandas, or build a small automation using what you have learned.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India