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")
tryholds the risky code.exceptruns for a matching error type.elseruns only when nothing failed.finallyalways 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
selfin 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.