Menu

Xử lý file trong Python: đọc, ghi và nối thêm an toàn

Cách đọc và ghi tệp trong Python: câu lệnh with, chế độ văn bản và nhị phân, cùng API hiện đại dựa trên đường dẫn an toàn hơn.

Tệp chỉ là chuỗi có kèm đường dẫn

Phần lớn chương trình đến lúc nào đó cũng cần đọc hoặc ghi tệp (file): cấu hình, dữ liệu người dùng, log, tệp CSV xuất ra, bộ nhớ đệm nhỏ. Python giữ việc này đơn giản với một hàm có sẵn duy nhất, open(), cộng với một khối with để đảm bảo mọi thứ được đóng gọn gàng.

Cùng đi qua những trường hợp thường gặp.

Đọc toàn bộ tệp

Cách đọc đơn giản nhất:

with open("notes.txt") as f:
    contents = f.read()

print(contents)

open("notes.txt") mở tệp để đọc (chế độ mặc định). Khối with nghĩa là Python sẽ đóng tệp khi bạn ra khỏi khối, kể cả khi có ngoại lệ xảy ra. Sau khi tệp đã đóng, bạn không thể dùng f nữa; nội dung văn bản nằm trong contents.

contents là một chuỗi duy nhất chứa toàn bộ tệp. Ổn với tệp nhỏ, nhưng không ổn với một tệp log 2 GB.

Đọc từng dòng

Với tệp lớn, hãy lặp qua từng dòng thay vì đọc hết vào bộ nhớ:

with open("big.log") as f:
    for line in f:
        if "ERROR" in line:
            print(line.strip())

Mỗi lần lặp trả về một dòng bao gồm cả ký tự xuống dòng ở cuối, vì vậy .strip() xuất hiện rất thường xuyên. Nếu bạn muốn một danh sách các dòng, f.readlines() làm được việc đó, nhưng thường thì lặp trực tiếp mới là điều bạn cần.

Ghi tệp

Để ghi, truyền chế độ vào làm đối số thứ hai:

with open("output.txt", "w") as f:
    f.write("first line\n")
    f.write("second line\n")

Có hai điều cần lưu ý:

  • "w" ghi đè. Nếu output.txt đã tồn tại, nội dung của nó biến mất ngay khi open chạy.
  • Bạn phải tự thêm ký tự xuống dòng. f.write("hello") ghi đúng năm ký tự đó, không có ký tự xuống dòng ở cuối.

Để ghi nối thêm thay vì ghi đè:

with open("output.txt", "a") as f:
    f.write("another line\n")

Bạn cũng có thể ghi nhiều dòng một lúc bằng writelines:

lines = ["a\n", "b\n", "c\n"]
with open("letters.txt", "w") as f:
    f.writelines(lines)

Quy tắc vẫn vậy: bạn tự thêm ký tự xuống dòng.

Dùng print để ghi vào tệp

print có đối số file, một cách dễ dàng để ghi mà không phải tự thêm ký tự xuống dòng:

with open("log.txt", "w") as f:
    print("started", file=f)
    print("finished", file=f)

Mỗi lần print ghi các đối số của nó kèm một ký tự xuống dòng ở cuối. Với đầu ra đơn giản, đây thường là cách gọn nhất.

Chế độ văn bản và nhị phân

Mặc định, open mở ở chế độ văn bản. Python giải mã byte thành chuỗi (mặc định dùng UTF-8 trên các nền tảng hiện đại) và tự xử lý việc chuyển đổi ký tự xuống dòng.

Với ảnh, PDF, tệp đã biên dịch hay bất cứ thứ gì không phải văn bản, hãy mở ở chế độ nhị phân bằng cách thêm "b" vào chế độ:

with open("image.png", "rb") as f:
    data = f.read()

print(len(data))  # number of bytes

with open("image_copy.png", "wb") as f:
    f.write(data)

Ở chế độ nhị phân, bạn nhận được các đối tượng bytes thay vì str, và Python không đụng đến ký tự kết thúc dòng.

Chỉ định encoding

Với tệp văn bản, hãy ghi rõ encoding. Năm 2026, UTF-8 gần như luôn là lựa chọn đúng:

with open("notes.txt", encoding="utf-8") as f:
    contents = f.read()

Nếu bạn làm việc với tệp cũ từ một nơi chỉ dùng Windows, bạn có thể gặp cả cp1252 hoặc latin-1. Đoán sai sẽ cho ra ký tự bị lỗi font hoặc một UnicodeDecodeError.

pathlib: cách hiện đại để làm việc với đường dẫn

Module pathlib của thư viện chuẩn cho bạn một API dễ dùng hơn so với chuỗi thuần:

from pathlib import Path

path = Path("notes.txt")

# Simple read and write.
contents = path.read_text(encoding="utf-8")
path.write_text("new contents\n", encoding="utf-8")

# Build paths without worrying about / vs \.
data_dir = Path("data")
log_path = data_dir / "today.log"

print(log_path)
print(log_path.exists())
print(log_path.suffix)       # ".log"
print(log_path.stem)         # "today"
print(log_path.parent)       # "data"

Path.read_text() và write_text() là cách viết tắt cho mẫu with open(...) as f khi bạn chỉ muốn đọc và ghi tất cả một lần. Để lặp từng dòng, bạn vẫn dùng open hoặc path.open().

Một ví dụ nhỏ từ đầu đến cuối

Đọc một danh sách mục từ tệp, lọc chúng, rồi ghi kết quả ra một tệp khác:

from pathlib import Path

source = Path("items.txt")
destination = Path("filtered.txt")

items = source.read_text().splitlines()
kept = [item for item in items if item and not item.startswith("#")]

destination.write_text("\n".join(kept) + "\n")

print(f"Kept {len(kept)} items out of {len(items)}")

splitlines() là người bạn đồng hành theo dòng của join. Nó tách chuỗi tại các ký tự xuống dòng mà không giữ lại chúng, rất tiện khi đằng nào bạn cũng sẽ nối các phần lại.

Khi có sự cố

Mở một tệp không tồn tại sẽ ném ra FileNotFoundError. Đọc một tệp mà bạn không có quyền sẽ ném ra PermissionError. Cả hai đều là lớp con của OSError, và OSError lại là lớp con của Exception. Chúng ta sẽ nói về cách xử lý ngoại lệ đúng cách ở trang tiếp theo.

Còn bây giờ, xem trước một chút việc xử lý lỗi trông như thế nào:

from pathlib import Path

path = Path("maybe.txt")

try:
    contents = path.read_text()
except FileNotFoundError:
    print("File doesn't exist - starting fresh.")
    contents = ""

print(repr(contents))

Những thói quen quan trọng

  • Luôn dùng with open(...) thay vì open thuần rồi tự đóng.
  • Luôn truyền encoding= cho tệp văn bản; mặc định là UTF-8.
  • Đọc tệp lớn theo từng dòng; đừng .read() cả tệp.
  • Với đường dẫn và các thao tác đọc ghi thường gặp, hãy dùng pathlib, nó sẽ giúp bạn đỡ phải xoay xở rất nhiều với chuỗi.

Câu hỏi thường gặp

Làm sao để đọc một tệp trong Python?

Dùng open() cùng câu lệnh with, câu lệnh này tự động đóng tệp khi bạn dùng xong. with open('file.txt') as f: contents = f.read() đọc toàn bộ tệp vào một chuỗi. Để đọc từng dòng, hãy lặp trực tiếp trên đối tượng tệp: for line in f:.

Các chế độ mở 'r', 'w' và 'a' khác nhau thế nào?

'r' là đọc (mặc định). 'w' là ghi: nó tạo tệp mới, hoặc xóa sạch nội dung nếu tệp đã tồn tại. 'a' là ghi nối thêm: nó thêm vào cuối tệp mà không xóa nội dung cũ. Thêm 'b' cho chế độ nhị phân hoặc '+' để vừa đọc vừa ghi.

Vì sao nên dùng with open thay vì chỉ open?

Câu lệnh with đảm bảo tệp được đóng ngay cả khi có lỗi xảy ra giữa chừng. Không có with, bạn phải tự gọi .close() và nhớ gọi nó cả trong các nhánh xử lý lỗi. with an toàn hơn và ít code hơn.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu