Python - 確認檔案是否存在

在開發 Python 程式時,時常會需要確認某個檔案是不是真的存在。

這篇文章會介紹幾種在 Python 中檢查檔案存在與否的方法,說明它們適合用在什麼場合,最後還會進行效能測試。

os.path.isfile()

當然,第一個想到的就是使用 os.path.isfile() 這個函數。image 1

1
2
3
4
5
6
import os.path

if os.path.isfile('/path/to/file'):
print("檔案存在")
else:
print("找不到檔案")

這個函式會回傳 True 如果指定的路徑是一個存在的一般檔案。
不過這裡有個地方值得注意的是,這個方法會追蹤符號連結,所以對同一個路徑,islink()isfile() 可能會同時回傳 True。

pathlib module

Python 3.4 之後,Python 提供了 pathlib 模組,讓我們可以更方便地操作路徑。

1
2
3
4
5
6
7
from pathlib import Path

my_file = Path("/path/to/file")
if my_file.is_file():
print("檔案存在")
else:
print("找不到檔案")

這個方法和 os.path.isfile() 類似,但是使用起來更為直觀。

我們再來看看 pathlib 的其他你可能也會用到的方法:

檢查路徑是否是資料夾

1
2
if my_file.is_dir():
print("資料夾存在")

檢查路徑是否存在,不管是檔案還是資料夾

1
2
if my_file.exists():
print("路徑存在")

try-except

有些情況下,直接試著開啟檔案然後處理可能發生的例外狀況也是可以考慮的做法。

1
2
3
4
5
6
try:
with open('/path/to/file', 'r') as f:
# 檔案存在,可以進行操作
pass
except FileNotFoundError:
print("找不到檔案")

這種寫法的好處是可以避免檢查和開啟檔案之間可能發生的 race condition。例如,如果在檢查檔案存在之後、嘗試開啟之前,檔案被刪掉或搬走了,程式就可能會出錯。

pathlib.resolve()

pathlib 還提供了一個 resolve() 方法,也可以用來檢查檔案是否存在:

1
2
3
4
5
6
7
8
from pathlib import Path

my_file = Path("/path/to/file")
try:
my_abs_path = my_file.resolve(strict=True)
print("檔案存在")
except FileNotFoundError:
print("找不到檔案")

這個方法會試著解析路徑,如果檔案不存在,就會拋出 FileNotFoundError 例外。

效能測試

在選擇使用哪種方法時,除了考慮程式碼的可讀性和功能性外,效能也是一個重要的考量因素。為了比較不同方法的效能,我們進行了一個簡單的測試。

為標準化測試結果,我使用 Google Cloud 一台 e2-medium 規格的機器,選擇 Ubuntu 22.04.4 LTS 作為作業系統。
這台虛擬機器有 1~2 個 vCPU 和 4 GB 記憶體,使用 SSD 永久磁碟 10 GB 作為儲存空間。

Python 版本則使用目前最新的 Python 3.10.4。

測試方法

我們使用 Python 的 timeit 模組來測量每種方法的執行時間。每種方法都會執行 100,000 次,以獲得更準確的平均執行時間。我們分別測試了檢查存在的檔案和不存在的檔案的情況。

以下是測試程式的核心部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import os
import timeit
from pathlib import Path

# 準備一個存在的檔案和一個不存在的檔案路徑
existing_file = "existing_file.txt"
non_existing_file = "non_existing_file.txt"

# 創建一個臨時檔案
with open(existing_file, "w") as f:
f.write("This is a test file.")

# 定義測試函數
def test_os_path(filename):
return os.path.isfile(filename)

def test_pathlib(filename):
return Path(filename).is_file()

def test_try_except(filename):
try:
with open(filename, 'r'):
return True
except FileNotFoundError:
return False

def test_pathlib_resolve(filename):
try:
Path(filename).resolve(strict=True)
return True
except FileNotFoundError:
return False

# 執行性能測試
number = 100000 # 每個測試執行的次數

print("檢查存在的檔案:")
print(f"os.path.isfile: {timeit.timeit(lambda: test_os_path(existing_file), number=number):.6f} 秒")
print(f"pathlib.is_file: {timeit.timeit(lambda: test_pathlib(existing_file), number=number):.6f} 秒")
print(f"try-except: {timeit.timeit(lambda: test_try_except(existing_file), number=number):.6f} 秒")
print(f"pathlib.resolve: {timeit.timeit(lambda: test_pathlib_resolve(existing_file), number=number):.6f} 秒")

print("\n檢查不存在的檔案:")
print(f"os.path.isfile: {timeit.timeit(lambda: test_os_path(non_existing_file), number=number):.6f} 秒")
print(f"pathlib.is_file: {timeit.timeit(lambda: test_pathlib(non_existing_file), number=number):.6f} 秒")
print(f"try-except: {timeit.timeit(lambda: test_try_except(non_existing_file), number=number):.6f} 秒")
print(f"pathlib.resolve: {timeit.timeit(lambda: test_pathlib_resolve(non_existing_file), number=number):.6f} 秒")

# 刪除臨時檔案
os.remove(existing_file)

測試結果

以下就是測試結果:

1
2
3
4
5
6
7
8
9
10
11
12
codingman@instance-20240719-xxxx:~$ python3.12 test.py 
檢查存在的檔案:
os.path.isfile: 0.259494 秒
pathlib.is_file: 0.722036 秒
try-except: 1.271036 秒
pathlib.resolve: 1.283478 秒

檢查不存在的檔案:
os.path.isfile: 0.259891 秒
pathlib.is_file: 0.752700 秒
try-except: 0.411024 秒
pathlib.resolve: 0.949050 秒

結果分析

  • os.path.isfile() 方法在兩種情況下都表現最佳,這可能是因為它是最底層的實現。
  • pathlib 的 is_file() 方法緊隨其後,雖然比 os.path.isfile() 慢一些,但提供了更多的功能和更好的物件導向介面。
  • try-except 方法在檢查存在的檔案時比較慢,而在檢查不存在的檔案時更慢。這是因為它實際上嘗試打開檔案,這個操作比單純檢查檔案是否存在要耗時更多。
  • pathlib.resolve() 方法的效能介於 pathlib.is_file() 和 try-except 之間。

結論

從單純效能的角度來看,os.path.isfile() 顯然是最快的選擇。然而,在實際應用中,我們還需要考慮其他因素:

  • 如果你的專案大量使用 pathlib,為了保持一致性,使用 pathlib.is_file() 可能更好。
  • 如果你需要立即對檔案進行操作,try-except 方法雖然較慢,但可以避免 race condition,可能是更安全的選擇。
  • pathlib.resolve() 提供了額外的功能(如解析符號連結),如果你需要這些功能,它可能是一個好選擇。

最後,請記住效能測試結果可能會因系統和環境的不同而有所變化。在選擇方法時,應該根據你的具體需求和使用場景來決定。

也許你也會想看看