Câu trả lời ngắn
Để kiểm tra một tệp (file) có tồn tại hay không, hãy dùng os.path.isfile() trong thư viện chuẩn. Hàm này trả về True khi đường dẫn trỏ tới một tệp thông thường đang tồn tại và False trong mọi trường hợp còn lại. Nó không bao giờ báo lỗi khi đường dẫn không tồn tại.
Đường dẫn được tính từ thư mục mà chương trình đang chạy (thư mục làm việc hiện tại), trừ khi bạn truyền vào một đường dẫn tuyệt đối như /home/ada/notes.txt hoặc C:\Users\Ada\notes.txt.
Phần còn lại của trang này nói về những cách khác để hỏi cùng câu hỏi, chúng khác nhau ra sao, và trường hợp bạn hoàn toàn không nên hỏi.
os.path.exists, isfile và isdir
Module os.path có ba hàm kiểm tra liên quan đến nhau, và chúng trả lời những câu hỏi hơi khác nhau:
| Hàm | Trả về True khi đường dẫn là |
|---|---|
os.path.exists(p) | bất cứ thứ gì đang tồn tại: một tệp, một thư mục hoặc một symlink còn hoạt động |
os.path.isfile(p) | một tệp thông thường đang tồn tại |
os.path.isdir(p) | một thư mục đang tồn tại |
Sự khác biệt lộ ra ngay khi có thư mục tham gia:
Đầu ra:
reports True False True
reports/summary.txt True True False
reports/old.txt False False False
os.path.exists("reports") là True dù reports là một thư mục. Nếu code của bạn sắp đọc đường dẫn đó như một tệp thì exists là phép kiểm tra sai: một thư mục vẫn vượt qua nó, và open("reports") sau đó sẽ thất bại (với IsADirectoryError trên Linux và macOS, PermissionError trên Windows). Chọn isfile khi bạn muốn nói tới tệp và isdir khi bạn muốn nói tới thư mục.
Thêm hai chi tiết về ba hàm os.path này:
- Symlink được đi theo. Một liên kết trỏ tới tệp có thật được tính là tệp; một liên kết hỏng (đích của nó đã mất) trả về
Falsevới cả ba hàm. - Nếu Python không được phép xem bên trong một thư mục, hàm kiểm tra trả về
Falsethay vì ném raPermissionError.Falsenghĩa là "không tìm thấy hoặc không truy cập được", chứ không hẳn là "không tồn tại".
Kiểm tra bằng pathlib
pathlib là API đường dẫn hướng đối tượng trong thư viện chuẩn, và phần lớn code mới đều dùng nó. Một đối tượng Path có đúng ba phép kiểm tra đó dưới dạng phương thức:
Path.exists(), Path.is_file() và Path.is_dir() hoạt động giống os.path.exists, isfile và isdir. Toán tử / nối các phần của đường dẫn bằng ký tự phân cách phù hợp với hệ điều hành, nên folder / "summary.txt" chạy được như nhau trên Windows, macOS và Linux. Xem làm việc với tệp để đọc và ghi tệp qua đối tượng Path.
Nên dùng cách nào? Nếu phần còn lại của code đã truyền qua lại các đối tượng Path, hãy tiếp tục với pathlib. Nếu bạn đang làm việc với chuỗi thông thường và các hàm của os, os.path.isfile cũng đúng y như vậy. Hai cách cho cùng một kết quả.
Vì sao kiểm tra trước có thể là nước đi sai
Lý do phổ biến nhất để kiểm tra tệp có tồn tại là để mở nó ngay sau đó:
import os
if os.path.isfile("config.txt"):
with open("config.txt") as f: # the file might be gone by now
settings = f.read()
else:
settings = ""
Cách này có một kẽ hở. Giữa lệnh gọi isfile() và lệnh gọi open(), một chương trình khác, một luồng khác hoặc chính người dùng có thể xóa hay đổi tên tệp. Phép kiểm tra báo True, lệnh mở vẫn thất bại, và chương trình của bạn vẫn dừng với FileNotFoundError. Kẽ hở này gọi là race condition, hay "time of check to time of use" (thời điểm kiểm tra khác thời điểm sử dụng). Trên laptop của bạn thì hiếm, nhưng trên một máy chủ bận rộn có nhiều tiến trình dùng chung một thư mục thì không hiếm chút nào.
Cách sửa là bỏ bước kiểm tra riêng và để chính open() làm việc kiểm tra. Python ném ra FileNotFoundError khi tệp không tồn tại, và bạn xử lý nó trong except:
Đầu ra:
theme=dark
(no settings file, using defaults)
Giờ chỉ còn một thao tác thay vì hai, nên không còn gì để "chạy đua" nữa. Phong cách này có tên trong cộng đồng Python: EAFP, "easier to ask forgiveness than permission" (xin lỗi dễ hơn xin phép). Phong cách ngược lại, kiểm tra trước, là LBYL, "look before you leap" (nhìn trước khi nhảy). Python nghiêng về EAFP khi làm việc với tệp vì chỉ hệ điều hành mới biết câu trả lời đúng vào lúc bạn mở tệp.
FileNotFoundError không phải là thứ duy nhất có thể hỏng. Một đường dẫn là thư mục sẽ ném ra IsADirectoryError (PermissionError trên Windows), và một tệp bạn không có quyền đọc sẽ ném ra PermissionError. Cả ba đều là lớp con của OSError, nên except OSError: bắt được mọi trường hợp "không mở được" cùng lúc. Trang ngoại lệ nói về cách chọn giữa bắt một lỗi cụ thể và bắt cả một họ lỗi.
Khi nào chỉ cần kiểm tra là đủ
Kiểm tra trước không phải lúc nào cũng sai. Đó là công cụ đúng khi bạn chỉ cần câu trả lời và không định thao tác với tệp ở dòng tiếp theo:
- chọn thông báo cần hiển thị ("Tìm thấy 3 bản lưu game")
- kiểm tra đường dẫn người dùng nhập trước khi bắt đầu một tác vụ dài
- bỏ qua phần việc đã làm xong, khi một câu trả lời cũ chỉ khiến bạn phải làm lại một lần
Quy tắc chung: nếu bạn sắp mở, đọc, ghi hoặc xóa tệp, hãy làm việc đó trong try. Nếu bạn chỉ muốn biết, isfile() hoặc Path.is_file() là đủ.
Chỉ tạo tệp khi tệp chưa tồn tại
Câu hỏi ngược lại cũng hay gặp: "ghi tệp này, nhưng không bao giờ ghi đè lên tệp đã có". Cách viết dễ nghĩ ra nhất lại có đúng kẽ hở như trên:
if not os.path.exists("report.txt"):
with open("report.txt", "w") as f: # another process may create it first
f.write("new report\n")
Chế độ "x" (tạo độc quyền) gộp hai bước làm một. Nó tạo tệp, hoặc ném ra FileExistsError nếu tên đó đã bị dùng:
Đầu ra:
attempt 1: created report.txt
attempt 2: report.txt already exists, left it alone
Với thư mục, os.makedirs(path, exist_ok=True) và Path(path).mkdir(parents=True, exist_ok=True) tạo thư mục cùng mọi thư mục cha còn thiếu, và không làm gì nếu thư mục đã có. Không cần gọi isdir() trước.
Lỗi thường gặp
- Dùng
exists()khi bạn muốn nói tới tệp. Một thư mục trùng tên cũng vượt qua phép kiểm tra. Hãy dùngisfile()hoặcPath.is_file(). - Quên đường dẫn tương đối bắt đầu từ đâu.
"data.txt"được tìm trong thư mục làm việc hiện tại, tức là nơi bạn khởi chạy chương trình, không nhất thiết là thư mục chứa script. Để tìm một tệp nằm cạnh script, hãy dựng đường dẫn từPath(__file__).parent. - Kiểm tra rồi mới mở. Tệp có thể thay đổi ở giữa. Hãy mở trong
tryvà xử lýFileNotFoundError. - Bắt mọi ngoại lệ.
except Exception:quanhopen()cũng che luôn lỗi gõ nhầm và bug trong đoạn code đọc tệp. Hãy bắtFileNotFoundErrorhoặcOSError.
Câu hỏi thường gặp
Làm sao kiểm tra một tệp có tồn tại trong Python?
Import os rồi gọi os.path.isfile("data.txt"), hàm này chỉ trả về True khi đó là một tệp thông thường đang tồn tại. Với pathlib, bạn viết Path("data.txt").is_file(). Dùng os.path.exists() hoặc Path.exists() khi một thư mục trùng tên cũng được tính.
os.path.exists và os.path.isfile khác nhau thế nào?
os.path.exists(p) là True với bất cứ thứ gì nằm ở đường dẫn đó: một tệp, một thư mục hoặc một symlink trỏ tới thứ có thật. os.path.isfile(p) chỉ là True với tệp thông thường, nên trả về False cho thư mục. os.path.isdir(p) là bản tương ứng dành cho thư mục.
Có nên kiểm tra tệp tồn tại trước khi mở không?
Thường là không. Tệp có thể bị xóa hoặc được tạo ra trong khoảng giữa lúc kiểm tra và lúc gọi open(), nên việc kiểm tra không đảm bảo được gì. Hãy mở tệp trực tiếp trong try và xử lý FileNotFoundError trong except. Một bước kiểm tra riêng chỉ hợp lý khi bạn chỉ cần câu trả lời, ví dụ để chọn thông báo cần hiển thị.
Làm sao tạo tệp chỉ khi tệp đó chưa tồn tại?
Mở tệp với chế độ "x": open("report.txt", "x"). Python sẽ tạo tệp, hoặc ném ra FileExistsError nếu đã có thứ gì ở đó. Việc kiểm tra và việc tạo diễn ra trong một bước, nên không chương trình nào khác chen vào giữa được.