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ả |
|---|---|
r | Chỉ đọc (mặc định). Báo lỗi nếu tệp không tồn tại. |
w | Ghi đè. Tạo mới nếu chưa tồn tại; xóa nội dung nếu đã có. |
a | Ghi nối cuối. Luôn đặt con trỏ ở cuối tệp. |
b | Chế độ nhị phân — bắt buộc cho tệp không phải văn bản (ảnh, video...). |
t | Chế độ văn bản (mặc định). |
+ | Mở để đọc & ghi đồng thời (ví dụ: r+, a+). |
x | Mở để 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:
- 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) - 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')