Xử lý tệp tin trong Python: Cơ chế, chế độ mở và thao tác nâng cao

Trong hệ thống máy tính, dữ liệu ứng dụng cần được lưu trữ vĩnh viễn trên thiết bị lưu trữ khối (như ổ cứng). Vì chương trình không thể truy cập phần cứng trực tiếp, hệ điều hành cung cấp giao diện tệp tin — một khái niệm trừu tượng hóa việc đọc/ghi lên đĩa. Khi lập trình bằng Python, ta làm việc với tệp thông qua các hàm hệ thống do hệ điều hành cung cấp.

Cơ chế mở tệp cơ bản

Mọi thao tác tệp đều tuân theo ba bước:

  • Mở tệp: Gọi open() để yêu cầu hệ điều hành cấp quyền truy cập và trả về một file object (đối tượng tệp).
  • Thao tác: Dùng phương thức như read(), write() trên đối tượng vừa nhận được.
  • Đóng tệp: Gọi close() để giải phóng tài nguyên hệ điều hành (file descriptor) và đảm bảo dữ liệu được ghi đầy đủ.

Quản lý tài nguyên an toàn với with

Việc quên gọi close() có thể dẫn đến rò rỉ tài nguyên hoặc mất dữ liệu chưa được flush. Câu lệnh with tự động xử lý đóng tệp ngay cả khi xảy ra ngoại lệ:

with open('input.txt', 'r', encoding='utf-8') as src, \
     open('output.txt', 'w', encoding='utf-8') as dst:
    content = src.read()
    dst.write(content.upper())
# Tệp tự động đóng khi thoát khối with

Chế độ mở tệp và ý nghĩa từng tùy chọn

Hàm open() chấp nhận chuỗi chế độ gồm các ký tự kết hợp:

Ký tự Mô tả
rChỉ đọc (mặc định). Báo lỗi nếu tệp không tồn tại.
wGhi đè. Tạo mới nếu chưa tồn tại; xóa nội dung nếu đã có.
aGhi nối cuối. Luôn đặt con trỏ ở cuối tệp.
bChế độ nhị phân — bắt buộc cho tệp không phải văn bản (ảnh, video...).
tChế độ văn bản (mặc định).
+Mở để đọc & ghi đồng thời (ví dụ: r+, a+).
xMở để ghi duy nhất — thất bại nếu tệp đã tồn tại.

Lưu ý quan trọng:

  • Khi dùng chế độ b, mọi dữ liệu đều là bytes. Không được chỉ định tham số encoding.
  • Chế độ + không thay đổi vị trí con trỏ mặc định: r+ bắt đầu từ đầu, a+ bắt đầu từ cuối.

Thao tác trên nội dung tệp

Các phương thức phổ biến:

  • read([n]): Đọc tối đa n ký tự (văn bản) hoặc byte (nhị phân). Không truyền n → đọc hết.
  • readline(): Đọc một dòng (bao gồm ký tự xuống dòng \n).
  • readlines(): Trả về danh sách các dòng.
  • write(data): Ghi chuỗi (văn bản) hoặc bytes (nhị phân).
  • writelines(iterable): Ghi nhiều chuỗi/bytes từ iterable — không tự thêm \n.

Điều khiển con trỏ và định dạng tệp

Phương thức liên quan đến vị trí con trỏ:

  • seek(offset, whence=0): Di chuyển con trỏ.
    • whence=0: Tính từ đầu tệp.
    • whence=1: Tính từ vị trí hiện tại (chỉ dùng trong chế độ nhị phân).
    • whence=2: Tính từ cuối tệp (chỉ dùng trong chế độ nhị phân).
  • tell(): Trả về vị trí hiện tại của con trỏ (tính bằng byte).
  • truncate([size]): Cắt ngắn tệp tại vị trí con trỏ (hoặc tại size nếu được chỉ định). Chỉ hoạt động trong chế độ ghi (w, a, r+...).

Thực hiện sao chép tệp nhị phân

Đây là cách viết công cụ cp hỗ trợ mọi loại tệp (văn bản, ảnh, video...):

import sys

def copy_file(src_path, dst_path):
    try:
        with open(src_path, 'rb') as src, open(dst_path, 'wb') as dst:
            # Sao chép từng khối 8192 byte để tiết kiệm bộ nhớ
            while chunk := src.read(8192):
                dst.write(chunk)
        print(f"✓ Copied '{src_path}' → '{dst_path}'")
    except FileNotFoundError:
        print(f"✗ Error: Source file '{src_path}' not found.")
        sys.exit(1)
    except PermissionError:
        print(f"✗ Error: Permission denied accessing files.")
        sys.exit(1)

if __name__ == '__main__':
    if len(sys.argv) != 3:
        print("Usage: python cp.py <source> <destination>")
        sys.exit(1)
    copy_file(sys.argv[1], sys.argv[2])

Giải pháp an toàn cho việc chỉnh sửa tệp

Vì hệ thống tệp không hỗ trợ "sửa trực tiếp", ta thường áp dụng một trong hai kỹ thuật:

  1. Tải toàn bộ vào bộ nhớ: Phù hợp với tệp nhỏ.
    def replace_in_file(filename, old, new):
          with open(filename, 'r', encoding='utf-8') as f:
              content = f.read()
          content = content.replace(old, new)
          with open(filename, 'w', encoding='utf-8') as f:
              f.write(content)
      
  2. Xử lý từng dòng: Hiệu quả với tệp lớn, tiết kiệm RAM.
    import os
    
      def replace_line_by_line(src, dst, old, new):
          with open(src, 'r', encoding='utf-8') as rf, \
               open(dst, 'w', encoding='utf-8') as wf:
              for line in rf:
                  wf.write(line.replace(old, new))
          os.replace(dst, src)  # Thay thế tệp gốc bằng bản mới
      

Đọc tệp theo dõi thời gian thực (tail -f)

Mô phỏng hành vi tail -f bằng cách sử dụng seek(0, 2) để nhảy đến cuối tệp rồi lặp lại:

import time

def tail_f(filename):
    with open(filename, 'r', encoding='utf-8') as f:
        f.seek(0, 2)  # Di chuyển con trỏ đến cuối tệp
        while True:
            line = f.readline()
            if line:
                print(line, end='')
            else:
                time.sleep(0.5)

# tail_f('app.log')

Thẻ: python file-io binary-mode with-statement seek

Đăng vào ngày 13 tháng 6 lúc 21:10