The file.writelines(lines) method writes an iterable sequence of strings (or bytes in binary mode) to an open file stream without returning a value.
The Classic Naming Trap: Despite its name, writelines() does not automatically append newline characters (\n) between items. If your sequence elements lack explicit line terminators, they will concatenate directly into a single continuous line.
The standard CPython signature for file.writelines() is:
file_object.writelines(lines)
| Component | Type | Required? | Behavior & Description |
|---|---|---|---|
lines |
Iterable[str] or Iterable[bytes] |
Yes | Any iterable yielding string objects (in text mode) or bytes objects (in binary mode). This includes lists, tuples, generator expressions, and custom iterators. |
| Return Value | None |
— | Unlike file.write(), writelines() does not return the count of written units; it returns None. |
| Exceptions | TypeError |
— | Raised immediately if any item in the iterable is not a string (e.g., an integer or float). |
Because writelines() does not insert line delimiters, passing a list of raw words results in a single run-on sentence:
names = ["Alex", "Ronald", "John"]
# Trap: Writing raw elements results in "AlexRonaldJohn"
with open("names_trap.txt", "w", encoding="utf-8") as f:
f.writelines(names)
# Solution 1: Generator expression adding explicit newlines on the fly
with open("names_clean.txt", "w", encoding="utf-8") as f:
f.writelines(f"{name}\n" for name in names)
# Solution 2: Joining with newlines via single write() call
with open("names_join.txt", "w", encoding="utf-8") as f:
f.write("\n".join(names) + "\n")
One of the greatest advantages of writelines() over write("\n".join(...)) is that it accepts lazy generators. Instead of allocating a massive concatenated string or list in RAM, Python pulls items one by one and streams them to the file buffer:
def generate_server_records(count: int):
for i in range(count):
yield f"RECORD_ID_{i:06d}: STATUS=OK, LATENCY=12ms\n"
with open("records_stream.log", "w", encoding="utf-8") as fob:
# Generates and writes lines lazily without pre-allocating memory
fob.writelines(generate_server_records(100000))
print("100,000 records written efficiently with zero memory overhead.")
When calling writelines() with write mode ('w'), the target file is truncated before writing starts. In append mode ('a'), the sequence is added seamlessly to the end of existing contents:
batch_initial = ["Row 1\n", "Row 2\n"]
batch_update = ["Row 3\n", "Row 4\n"]
with open("dataset.txt", "w", encoding="utf-8") as f:
f.writelines(batch_initial)
with open("dataset.txt", "a", encoding="utf-8") as f:
f.writelines(batch_update)
with open("dataset.txt", "r", encoding="utf-8") as f:
print("All lines in file:", len(f.readlines())) # 4
Evaluate the four primary patterns for saving collections of lines to a file in Python:
| Pattern | Memory Footprint | Auto-Newline? | Best Suited For |
|---|---|---|---|
f.writelines(gen) |
O(1) — Constant | No (manual \n) |
Large streaming datasets, database cursor dumping, log generation. |
f.write('\n'.join(list)) |
O(N) — High (builds string) | Manual delimiter | Small to medium lists already in memory where speed is paramount. |
for item in list: f.write(item) |
O(1) — Constant | No | Loops with conditional formatting or complex per-item transformations. |
print(*list, sep='\n', file=f) |
O(N) — High | Yes | Interactive scripts and rapid debugging prototypes. |
Combine header and formatted record rows using writelines() without loading extra dependencies:
def export_students_csv(filepath: str, students: list[dict]) -> None:
header = "ID,Name,Grade,Status\n"
rows = (f"{s['id']},{s['name']},{s['grade']},{s['status']}\n" for s in students)
with open(filepath, "w", encoding="utf-8") as f:
f.write(header)
f.writelines(rows)
roster = [
{"id": 1, "name": "Ravi", "grade": "A", "status": "Enrolled"},
{"id": 2, "name": "Sara", "grade": "A+", "status": "Enrolled"},
]
export_students_csv("roster.csv", roster)
print("Student CSV exported successfully.")
In binary mode, writelines() accepts an iterable yielding raw bytes objects:
packets = [
b"\xAA\xBB\xCC\xDD", # Sync word
b"\x00\x01\x02\x03", # Packet header
b"\xFF\xFE\xFD\xFC", # Checksum
]
with open("network.pcap", "wb") as bin_stream:
bin_stream.writelines(packets)
print("Binary packet sequence written cleanly.")
Unlike print(), writelines() does not call str() on elements. If your list contains numbers, tuples, or custom objects, Python immediately raises TypeError: writelines() argument must be a sequence of strings.
# WRONG: List contains integers
try:
with open("numbers.txt", "w") as f:
f.writelines([10, 20, 30])
except TypeError as err:
print("Caught expected error:", err)
# CORRECT: Convert items with str() in generator
with open("numbers.txt", "w") as f:
f.writelines(f"{n}\n" for n in [10, 20, 30])
Execute this self-contained test harness verifying generator emission, newline preservation, and binary writing:
import tempfile
import os
class WritelinesVerifier:
@staticmethod
def write_lines(path: str, items: list[str]) -> None:
with open(path, "w", encoding="utf-8") as stream:
stream.writelines(f"{item}\n" for item in items)
# Verification unit tests
with tempfile.TemporaryDirectory() as td:
v = WritelinesVerifier()
test_file = os.path.join(td, "test.txt")
# Test 1: Writing lines with newline termination
v.write_lines(test_file, ["Alpha", "Beta", "Gamma"])
with open(test_file, "r", encoding="utf-8") as f:
assert f.read() == "Alpha\nBeta\nGamma\n"
# Test 2: Binary writelines
bin_file = os.path.join(td, "test.bin")
with open(bin_file, "wb") as f:
f.writelines([b"\x01", b"\x02", b"\x03"])
with open(bin_file, "rb") as f:
assert f.read() == b"\x01\x02\x03"
print("All file.writelines() unit assertions passed successfully!")
Open in Google Colab View on GitHub
Run all writelines exercises interactively, test generator memory optimizations, contrast writelines with write and join, and experiment with binary buffers in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.