Học bằng cách đọc theo thứ tự

File I/O — Đọc và ghi an toàn với with open()

Học file I/O trong Python. with open() tự đóng, khác biệt giữa read / readlines / readline, mode w / a, và pattern đọc-xử lý-ghi lưu sang file riêng — kèm thực hành.

Phần lớn dữ liệu mà chương trình xử lý sống trên đĩa dưới dạng file. Đọc một danh sách công việc, append vào một log, load một config — tất cả đều là thao tác hằng ngày.

Trong Python, with open() cho phép bạn làm điều này trong một đơn vị cú pháp an toàn. Bài này bao quát dạng cơ bản của with open(), ba phương thức đọc (read / readlines / readline), ghi (w) và append (a), và xử lý đường dẫn có thư mục — bằng cách thực sự chạy code với file.

with open() mở và đóng file an toàn

Mở file là thao tác khiến OS giữ một resource, và một khi đã mở, bạn phải đóng. Quên là gây cạn file descriptor, hoặc dữ liệu ghi kẹt trong buffer và không bao giờ tới đĩa.

Mở grab resource của OS — đóng là hợp đồng
open()OS cấpFDread / writeclose()giải phóngopen()OS cấpFDread / writequên đóng-> FD cạn+ buffer chưa flushbình thường
open() khiến OS cấp phát một file descriptor (FD). Cho tới close(), FD vẫn bị giữ và writes vẫn buffered, chưa flush. Quên đóng là rò rỉ resource nghiêm trọng.

File descriptor (FD) là gì?

FD (file descriptor)ID số nguyên mà OS gán cho mỗi file đang mở. Mỗi open() cấp một FD mới; close() trả nó về OS. OS đặt giới hạn trên mỗi process (mặc định Linux khoảng 1.024). Vượt giới hạn đó và các lời gọi open() mới bắt đầu thất bại với OSError: Too many open files. Mối nguy của quên đóng chính là: giữ một resource hữu hạn mãi mãi.

Viết dạng with open(path, mode) as f: và file tự đóng ngay khi bạn rời block, nên không thể quên. f trong as f là biến nhận object file; bên trong block with, bạn đọc và ghi qua f.

Đường dẫn dùng dấu gạch chéo / để tách thư mục. open("data/tasks.txt", "r") mở tasks.txt bên trong thư mục data dưới thư mục hiện tại. Ngay cả trên Windows, quy ước Python dùng / — bên trong, nó được dịch theo OS.

Luồng của with open()
with open(path, mode)bindas fgọi f.read()hoặc f.write()tựđóngrời block
Vào with mở file và bind nó với as f. Rời block — bình thường hoặc qua exception — close() chạy tự động.

Đọc — read / readlines / readline

Để đọc file, dùng r (read) cho tham số thứ hai của open(path, "r"). Object file trả về có ba phương thức đọc cho các nhu cầu khác nhau.

- f.read() — đọc toàn bộ file thành một chuỗi.

- f.readlines() — đọc thành list các dòng (chuỗi).

- f.readline() — đọc một dòng. Lời gọi lặp lại tiến tới dòng kế; chuỗi rỗng "" báo hiệu end of file.

Ba phương thức đọc trả về cái gì
f.read()f.readlines()f.readline()một strtoàn vănlist cácdòngmột str(gọi tiếp =dòng kế)
read trả chuỗi, readlines trả list, readline trả một chuỗi mỗi lần gọi. Chọn theo kích thước dữ liệu.
# Lấy toàn bộ file thành một chuỗi
with open("data/tasks.txt", "r") as f:
    content = f.read()
print(type(content))     # <class 'str'>

# Lấy list các dòng
with open("data/tasks.txt", "r") as f:
    lines = f.readlines()
print(type(lines))       # <class 'list'>

# Lấy một dòng tại một thời điểm (gọi tiếp -> dòng kế)
with open("data/tasks.txt", "r") as f:
    first  = f.readline()   # "Viết báo cáo\n"
    second = f.readline()   # "Trả lời email\n"

Từ đây trở đi, console chạy trên file system ảo (VFS) phía trình duyệt

Console bên phải là file system ảo trong trình duyệt — các file như data/tasks.txt đã được dàn sẵn cho bạn. Để chạy cùng code trên PC của bạn, tạo thư mục data/ cạnh file Python của bạn (ví dụ main.py) và đặt tasks.txt trong đó trước khi chạy. Cú pháp đường dẫn ("data/tasks.txt" với /) giống hệt Python thật.

Click nút 📂 Files ở đầu console bên phải, và bạn sẽ thấy hai file đã dàn sẵn data/tasks.txt (4 việc) và data/log.txt (3 dòng lịch sử).

① Dùng with open("data/tasks.txt", "r") as f: để mở file ở mode đọc.

② Đọc tất cả với f.read() và lưu vào content.

③ In với print(content).

(Khi chạy đúng, phần giải thích sẽ hiện ra.)

Python Editor

Chạy code để xem đầu ra

Khi file là một bản ghi mỗi dòng (CSV không header, list cách nhau bởi newline, log lines), f.readlines() tiện. Nó trả list các chuỗi, mỗi cái một dòng, và mỗi phần tử giữ \n ở cuối.

In nó trực tiếp tạo cách dòng đôi, nên pattern chuẩn là dùng .rstrip("\n") để bỏ newline cuối.

Dùng data/log.txt (3 dòng lịch sử). Đọc nó thành list với readlines() và in mỗi dòng có đánh số. Đây là pattern điển hình cho log views và format báo cáo vận hành.

① Mở với with open("data/log.txt", "r") as f: và lưu f.readlines() vào lines.

② Lặp với enumerate(lines, start=1) để đánh số bắt đầu từ 1.

③ Bỏ newline cuối với .rstrip() và in dạng f"{n}: {body}".

Python Editor

Chạy code để xem đầu ra

File lớn? Đọc từng dòng — readline và walrus

f.read()f.readlines() load toàn file vào memory. Tốt cho file cỡ config, nhưng một log server nhiều GB có thể crash chương trình do hết memory.

f.readline() load chỉ một dòng tại một thời điểm, nên dùng memory chỉ ở mức một dòng. Khi readline() tới end of file, nó trả chuỗi rỗng "", mà bạn dùng làm điều kiện thoát vòng lặp.

readline() đọc từng dòng
file(nhiều GB)readline()-> dòng 1memory:dòng 1(tiếp tục)readline()-> dòng 2memory:dòng 2tới EOFreadline()-> ""vòng lặpkết thúc
Đọc một dòng, xử lý nó, ghi đè bằng dòng kế — toàn file không bao giờ sống trong memory. Ngay cả file nhiều GB chỉ dùng «một dòng dài nhất» memory. EOF trả "" để báo vòng lặp dừng.
# Phong cách kinh điển: break trên chuỗi rỗng
with open("data/log.txt", "r") as f:
    while True:
        line = f.readline()
        if not line:           # chuỗi rỗng = EOF
            break
        print(line.rstrip())

Toán tử walrus := viết tương tự với ít hơn một dòng

Python 3.8 giới thiệu toán tử walrus (:=), cho phép bạn gán và kiểm tra trong một biểu thức. Viết while line := f.readline(): nghĩa là «đặt kết quả của readline() vào line, và tiếp tục lặp khi nó còn không rỗng» — toàn vòng lặp trong một dòng.

Bây giờ đọc data/tasks.txt với readline() + toán tử walrus, một dòng một lần. Use case thực tế là log khổng lồ; ta dùng file tasks nhỏ để xem cơ chế.

① Mở with open("data/tasks.txt", "r") as f:.

② Lặp với while line := f.readline(): để mỗi dòng vào line.

③ Bỏ newline cuối với .rstrip() và in: print(line.rstrip()).

Python Editor

Chạy code để xem đầu ra

Ghi — mode w và mode a

Ghi chỉ là open() với tham số thứ hai khác. Hai mode mang phần lớn trọng lượng thực tế.

- "w" (write) — tạo mới hoặc ghi đè. Nội dung cũ bị phá hủy.

- "a" (append) — append vào cuối. Nội dung cũ giữ nguyên.

Cũng có hai phương thức ghi. f.write(s) ghi chuỗi s trực tiếp; f.writelines(lst) ghi list các chuỗi trong một lần. Nếu đường dẫn bao gồm thư mục, file được tạo trong đó (thư mục phải đã tồn tại).

w vs a
trước:ABCopen("w")f.write("XYZ")sau:XYZtrước:ABCopen("a")f.write("XYZ")sau:ABCXYZghi đèappend
w xóa nội dung cũ ngay khi bạn mở. a append từ cuối. Chọn nhầm có thể làm bay log quan trọng — hãy có chủ ý.
# mode w: ghi từng chuỗi một
with open("data/done.txt", "w") as f:
    f.write("Viết báo cáo\n")
    f.write("Trả lời email\n")

# mode w: ghi list cùng lúc với writelines
with open("data/done.txt", "w") as f:
    f.writelines(["Viết báo cáo\n", "Trả lời email\n", "Dự họp\n"])

# mode a: append vào cuối
with open("data/done.txt", "a") as f:
    f.write("Đi mua sắm\n")

Newline \n không được thêm tự động

Khác với print(), cả f.write()f.writelines() không thêm newline cho bạn. Gọi f.write("Hello") hai lần cho bạn HelloHello trong file. Chèn \n rõ ràng ở bất cứ đâu bạn thực sự muốn xuống dòng.

Tạo done.txt mới (bản ghi việc đã hoàn thành) bên trong thư mục data/, ghi vào nó, và đọc lại. Thư mục data/ đã tồn tại, nên dùng nó trực tiếp trong đường dẫn.

① Mở với with open("data/done.txt", "w") as f:.

② Sau f.write("Việc đã xong:\n"), ghi f.write("Viết báo cáo\n") rồi f.write("Trả lời email\n") — tổng cộng ba lần ghi.

③ Mở lại với with open("data/done.txt", "r") as f: riêng và print(f.read()) để hiển thị toàn nội dung.

④ Sau khi chạy, click nút 📂 Files ở header — bạn sẽ thấy data/done.txt trong panel.

Python Editor

Chạy code để xem đầu ra

Tái tạo trạng thái của data/done.txt từ thực hành trước, sau đó append item mới vào cuối. Trải nghiệm khác biệt giữa wa trong một script.

① Bắt đầu với with open("data/done.txt", "w") as f:f.write("Việc đã xong:\nViết báo cáo\n") để đặt nội dung ban đầu (cái này reset file mỗi lần chạy).

② Sau đó với with open("data/done.txt", "a") as f:, append f.write("Trả lời email\n").

③ Cuối cùng, mở lại với "r"print(f.read()) để xác nhận.

Python Editor

Chạy code để xem đầu ra

Đi xa hơn — Phân tích file và lưu kết quả sang file khác

Một trong những pattern phổ biến nhất trong công việc thực là đọc file input, làm phân tích, và ghi kết quả sang file riêng. Chỉ cần dùng open() hai lần — một với "r", một với "w". Phong cách sạch nhất là đặt input → process → output vào ba block with riêng.

# Phân tích data/log.txt và lưu kết quả sang data/log_summary.txt

# (1) Input: đọc log thành list các dòng
with open("data/log.txt", "r") as src:
    lines = src.readlines()

# (2) Process: đếm dòng, lấy entry đầu/cuối
total   = len(lines)
first   = lines[0].rstrip()
last    = lines[-1].rstrip()
summary = f"count: {total}\nfirst: {first}\nlast: {last}\n"

# (3) Output: ghi sang file khác
with open("data/log_summary.txt", "w") as dst:
    dst.write(summary)

print("Saved analysis to data/log_summary.txt")

data/products.txt chứa 4 dòng dạng "name,price". Đọc nó, tính tổng, và ghi kết quả sang file riêng data/total.txt.

① Mở với with open("data/products.txt", "r") as f: và lấy list với f.readlines().

② Cho mỗi dòng, bỏ newline cuối với .rstrip() và split trên "," để lấy name và price. Price là chuỗi, nên convert với int() trước khi cộng.

③ Mở with open("data/total.txt", "w") as f: và ghi kết quả với f.write(f"total: {total}\n").

④ Xác nhận bằng cách mở với "r"print(f.read()).

Python Editor

Chạy code để xem đầu ra

encoding="utf-8" là chuẩn cho mã hóa text

Trên Python thật, bạn thường viết open(path, "r", encoding="utf-8") để chỉ định encoding rõ ràng. Cố đọc file Shift-JIS dưới dạng UTF-8 báo UnicodeDecodeError, và ngược lại tạo mojibake im lặng. Chuẩn thế giới là UTF-8 — viết file mới ở UTF-8 mặc định. Môi trường trình duyệt hard-code UTF-8 bên trong nên bỏ encoding= chạy được ở đây, nhưng hãy biến thành thói quen khi viết script local.

QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Tham số thứ hai "r" trong with open("data/notes.txt", "r") as f: nghĩa là gì?

Câu 2Khi bạn gọi f.readline() lặp lại và file kết thúc, giá trị nào được trả về?

Câu 3Nếu data/done.txt đã có nội dung và bạn mở với with open("data/done.txt", "w") as f:, chuyện gì xảy ra với nội dung cũ?