Python Zero to Advanced #6: Strings & File Handling (with open, CSV, JSON)
Most real-world data arrives as text: user input, log files, CSV exports, JSON from an API. In this part we first sharpen our string skills, then learn how to read and write files the safe, Pythonic way, and finally process two of the most common data formats, CSV and JSON, using only the standard library.
String essentials
Strings are immutable sequences of Unicode characters. Every "modifying" method returns a new string. You can index and slice them exactly like lists.
s = " Hello, Python World! "
print(repr(s.strip()))
print(s.strip().lower())
print(s.strip().upper())
print(s.strip().replace("World", "Developers"))
print(s.count("o"), s.find("Python"), s.find("Java"))
word = "Python"
print(word[0], word[-1], word[1:4], word[::-1])
print(word.startswith("Py"), word.endswith("on"))
print("yth" in word)
Output:
'Hello, Python World!'
hello, python world!
HELLO, PYTHON WORLD!
Hello, Python Developers!
3 9 -1
P n yth nohtyP
True True
True
Split and join
split() breaks a string into a list, and join() glues a list of strings back together. Together they are the bread and butter of text processing.
line = "budi,siti,andi"
names = line.split(",")
print(names)
print(" | ".join(n.title() for n in names))
sentence = "Python is fun"
print(sentence.split()) # splits on any whitespace
print("-".join(sentence.split()))
csv_row = "2026-10-13;Coffee;18000"
date, item, price = csv_row.split(";")
print(item, int(price) * 2)
Output:
['budi', 'siti', 'andi']
Budi | Siti | Andi
['Python', 'is', 'fun']
Python-is-fun
Coffee 36000
Useful checks and formatting
print("12345".isdigit(), "abc".isalpha(), "abc123".isalnum())
print("hello world".title())
print("python".center(14, "*"))
print("7".zfill(3))
text = """Multi-line strings
keep their line breaks."""
print(text)
print(len("Halo"), len("café"))
path = r"C:\new\folder" # raw string: backslashes kept
print(path)
Output:
True True True
Hello World
****python****
007
Multi-line strings
keep their line breaks.
4 4
C:\new\folder
Reading and writing files with "with open"
open() returns a file object. Always use it inside a with block: the file is closed automatically when the block ends, even if an error occurs. Always pass encoding="utf-8" so your code behaves the same on every operating system.
| Mode | Meaning |
|---|---|
"r" | Read (default). Error if the file does not exist. |
"w" | Write. Creates the file or overwrites it. |
"a" | Append to the end of the file. |
"x" | Create; error if the file already exists. |
"b" | Binary mode, combined like "rb". |
# write
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("Buy coffee\n")
f.write("Call Siti\n")
# append
with open("notes.txt", "a", encoding="utf-8") as f:
f.write("Push code\n")
# read everything at once
with open("notes.txt", encoding="utf-8") as f:
content = f.read()
print(repr(content))
# read line by line (memory friendly for big files)
with open("notes.txt", encoding="utf-8") as f:
for number, line in enumerate(f, start=1):
print(number, line.rstrip("\n"))
Output:
'Buy coffee\nCall Siti\nPush code\n'
1 Buy coffee
2 Call Siti
3 Push code
Each line read from a file still ends with \n, which is why we call rstrip("\n"). For paths, the modern pathlib module is very convenient:
from pathlib import Path
p = Path("notes.txt")
print(p.exists(), p.suffix, p.stem)
lines = p.read_text(encoding="utf-8").splitlines()
print(lines)
print(Path("data") / "2026" / "report.csv")
Output:
True .txt notes
['Buy coffee', 'Call Siti', 'Push code']
data/2026/report.csv
Working with CSV
The csv module handles quoting, commas inside values, and line endings for you. Open CSV files with newline="" as the docs recommend. DictWriter and DictReader let you work with rows as dictionaries.
import csv
rows = [
{"name": "Budi", "city": "Jakarta", "spent": 55000},
{"name": "Siti", "city": "Bandung, West Java", "spent": 65000},
{"name": "Andi", "city": "Medan", "spent": 15000},
]
with open("customers.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["name", "city", "spent"])
writer.writeheader()
writer.writerows(rows)
with open("customers.csv", encoding="utf-8") as f:
print(f.read())
with open("customers.csv", newline="", encoding="utf-8") as f:
reader = csv.DictReader(f)
total = 0
for row in reader:
total += int(row["spent"]) # values are always strings
print(row["name"], "->", row["city"])
print("Total:", total)
Output:
name,city,spent
Budi,Jakarta,55000
Siti,"Bandung, West Java",65000
Andi,Medan,15000
Budi -> Jakarta
Siti -> Bandung, West Java
Andi -> Medan
Total: 135000
Notice how the city containing a comma was automatically wrapped in quotes, and that every value read back is a string, so we convert spent with int().
Working with JSON
JSON maps naturally to Python: objects become dicts, arrays become lists, true/false/null become True/False/None. Use dumps/loads for strings and dump/load for files.
import json
config = {
"app": "notes",
"debug": False,
"base_url": "https://api.example.com",
"retries": 3,
"owners": ["budi@example.com", "siti@example.com"],
"timeout": None,
}
text = json.dumps(config)
print(text)
with open("config.json", "w", encoding="utf-8") as f:
json.dump(config, f, indent=2)
with open("config.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded == config)
print(loaded["owners"][1], loaded["timeout"])
data = json.loads('{"name": "Dewi", "skills": ["python", "sql"], "active": true}')
print(data["skills"], data["active"])
Output:
{"app": "notes", "debug": false, "base_url": "https://api.example.com", "retries": 3, "owners": ["budi@example.com", "siti@example.com"], "timeout": null}
True
siti@example.com None
['python', 'sql'] True
Use indent=2 for human-readable files and ensure_ascii=False if you want characters like "é" written as-is instead of escape sequences.
Common mistakes
- Forgetting to close files when not using
with. Just always usewith. - Opening with
"w"by mistake wipes the file. Use"a"to append. - Omitting
encoding="utf-8": the default encoding differs on Windows and causesUnicodeDecodeError. - Splitting CSV lines manually with
split(",")breaks on quoted values. Use thecsvmodule. - Expecting strings to change in place:
name.upper()alone does nothing; assign the result.
Exercise
Read notes.txt, turn each line into a to-do item with an ID and a done flag, and save it as todos.json.
import json
with open("notes.txt", encoding="utf-8") as f:
todos = [
{"id": i, "title": line.strip(), "done": False}
for i, line in enumerate(f, start=1)
if line.strip()
]
with open("todos.json", "w", encoding="utf-8") as f:
json.dump(todos, f, indent=2)
with open("todos.json", encoding="utf-8") as f:
print(f.read())
Output:
[
{
"id": 1,
"title": "Buy coffee",
"done": false
},
{
"id": 2,
"title": "Call Siti",
"done": false
},
{
"id": 3,
"title": "Push code",
"done": false
}
]
Summary
- Strings are immutable; master
strip,split,join,replace, and slicing. - Always use
with open(..., encoding="utf-8")and pick the right mode. - Use
csv.DictReader/DictWriterfor CSV andjson.load/dumpfor JSON.
Files can be missing and JSON can be malformed. In Part 7 we will learn error handling with try/except, then organise code into modules and packages and manage dependencies with virtualenv and pip.
Related articles
PostgreSQL Zero to Advanced #5: Aggregation, GROUP BY & HAVING
Turn raw orders into business numbers: total revenue, average order value, sales per month and top customers using COUNT, SUM, AVG, GROUP BY, HAVING and FILTER.
PHP Zero to Advanced #5: Arrays & Array Functions
Master PHP arrays: indexed and associative arrays, nested data, destructuring, and the essential functions array_map, array_filter, array_reduce, usort, and friends.
Ruby on Rails Zero to Advanced #5: Routing, Controllers & Views
Follow a request through Rails: define routes, use resources, read params in controllers, and render dynamic HTML with ERB views and partials.