Python pathlib 教學:檔案路徑操作超簡單,不再煩惱前斜線或後斜線!

by 好豪
Published: Last Updated on
  • 如果你用 Mac 或 Linux 作業系統,你的檔案路徑會使用前斜線(/)
  • 如果位在 Windows 作業系統,則是反斜線(\)。

那麼,當你想寫個在不同作業系統都能操作檔案路徑的 Python 程式時,該怎麼辦?

下方是一個以字串處理來操作路徑的 Python 範例,需要根據作業系統使用不同的字串處理方法,不太有效率:

# 範例:查詢檔案所在資料夾的路徑
## Windows 是後斜線
>>> 'C:\Users\haohao\test.txt'.rsplit('\\', maxsplit=1)
['C:\\Users\\haohao', 'test.txt']

## Mac 或 Linux 是前斜線
>>> '/Users/haohao/test.txt'.rsplit('/', maxsplit=1)
['/Users/haohao', 'test.txt']

在 Python 3.4 版本之後,將 pathlib 納入了標準函式庫,使用 pathlib 內的 Path 類別就能讓你做到方便的檔案路徑操作簡易的檔案讀寫、並且幫你處理好上面範例的跨作業系統問題!

好豪將在這篇筆記用幾項簡單範例說明 pathlib 的威力,讓你可以立即上手,並且介紹深度學習套件 fastai 如何運用 pathlib 來提升工作效率。



pathlib 的 Path 類別介紹

物件導向的檔案路徑操作

在 pathlib 將檔案路徑透過 Path 類別包裝之後,就可以使用各種方便的檔案路徑操作。

## 大多數常用操作,只需要用到 Path 這個類別
>>> from pathlib import Path 
## 引數傳入你要指向的位置,此例指向桌面
>>> p = Path('./Desktop') 
>>> p
PosixPath('Desktop')
## resolve() 找出絕對路徑
>>> p.resolve() 
PosixPath('/Users/haohao/Desktop')

## 若沒有傳入引數,預設指向開啟 Python 的位置
>>> p = Path() 
>>> p
PosixPath('.')
>>> p.resolve() 
PosixPath('/Users/haohao/Desktop')

跨作業系統

若作業系統不同,Path 會幫你以不同方式處理檔案路徑。

  • 不論哪個作業系統,在 Path 物件內部,一律用前斜線(/)儲存路徑資料
  • 然而,當你用 print() 或是 str() 來表示 Path 的路徑字串,Path 物件會幫你依據程式執行所在的作業系統決定相對應輸出字串
## Windows 執行結果 -> 建立 WindowsPath
>>> p = Path()
>>> p
WindowsPath('.')
## 不論任何作業系統,內部儲存的路徑表示是前斜線
>>> p.resolve() 
WindowsPath('C:/Users/haohao/') 
## print() 或 str() 會根據執行的作業系統決定
## 此處 print() 結果是 Windows 所用的後斜線(\)
>>> print(p.resolve()) 
C:\Users\haohao\
## 使用 str() 時,後斜線會用跳脫字元表示
>>> str(p.resolve()) 
'C:\\Users\\haohao\\'

## 就算用後斜線建立 Path 物件,Path 內部儲存路徑依然是用前斜線
## 後斜線記得要跳脫(\\)
>>> p = Path('C:\\Users\\haohao')
>>> p
WindowsPath('C:/Users/haohao')
## 用 r'' (原始字串)就不用跳脫
>>> p = Path(r'C:\Users\haohao')
>>> p
WindowsPath('C:/Users/haohao')
## 建立 Path 可用前斜線表示 Windows 檔案路徑
>>> p = Path('C:/Users/haohao')
>>> p
WindowsPath('C:/Users/haohao')

## Mac 或 Linux 執行結果 -> 建立 PosixPath
>>> p
PosixPath('.')
>>> p.resolve() 
PosixPath('/Users/haohao/Desktop')

透過前斜線(/)來連接子路徑的特殊語法

Path 物件可以用前斜線(/)來延伸檔案子目錄路徑,Path 可以指向資料夾、也可以指向檔案

請注意:不管檔案或資料夾是否存在,Path 都可以指向它。

>>> p = Path('./Desktop')
>>> p / 'test.txt' # 使用前斜線(/)與字串,就能指向檔案子目錄路徑
PosixPath('Desktop/test.txt')
>>> p / 'test_dir' / 'test.txt' 
PosixPath('Desktop/test_dir/test.txt')
>>> (p / 'test_dir' / 'test.txt').resolve()
PosixPath('/Users/haohao/Desktop/test_dir/test.txt')

## 不管檔案或資料夾是否存在,都可以創造該路徑的 Path 物件
## 操作檔案(例如刪除、讀寫)之前,用 exists() 檢查該路徑目標是否存在
>>> p.resolve()
PosixPath('/Users/haohao/Desktop')
>>> (p / 'foo.txt').exists()
False
>>> (p / 'test.txt').exists() # 桌面上確實存在 test.txt 檔案
True

檔案路徑字串操作

使用 Path 的函式可以很快地從檔案路徑字串中取出你需要的資訊。除了學習以下範例,pbpython 的 Cheat Sheet 也有很好記的圖片說明。

>>> p = (p / 'test.txt').resolve()
>>> p
PosixPath('/Users/haohao/Desktop/test.txt')
>>> p.parent
PosixPath('/Users/haohao/Desktop')
>>> p.stem
'test'
>>> p.suffix
'.txt'
>>> p.name
'test.txt'

簡易檔案讀寫

Path 除了對檔案路徑進行方便的字串處理,還可以進行檔案操作,包括創造、刪除檔案等等。如果你熟悉 Linux 的 Shell 操作,會發現 Path 使用的函式名稱跟 Linux Shell 指令很像!

# 範例:創造一個檔案,寫一句話,再把它刪除掉
>>> p = Path('/Users/haohao/Desktop')
>>> file_name = 'test.txt'
>>> p = p / file_name
>>> p
PosixPath('/Users/haohao/Desktop/test.txt')
>>> p.exists() # 先確認這個檔案確實不存在
False

## 創造指向此路徑的檔案
>>> p.touch() # 創造 test.txt
## 寫入資料(return 寫入字串的長度)
>>> p.write_text('hello world!')
12
>>> p.exists() # 現在此檔案存在了
True
## 讀取資料
>>> p.read_text()
'hello world!'
## 刪除檔案
>>> p.unlink()
>>> p.exists() # 此檔案確實被刪掉了
False

Path 的讀寫除了用 write_text() 與 read_text(),你也可以使用 open()。

>>> with p.open() as f: f.readline()
...
'hello world!'

小提示

PurePath 與 Path 差別

如果你翻閱 Python pathlib 官方文件,會看到 Path 跟 PurePath 兩種類別、寫了不同的可用函式,實際上,Path 是 PurePath 的子類別(如圖示),PurePath 能用的函式、Path 幾乎都能用,所以大多時候用 Path 就好、你在 官方文件 看到的函式 Path 都能用。

Path 是繼承自 PurePath 的子類別(Source: Python pathlib doc

PurePath 與 Path 在功能上的差別:PurePath 主要只是提供方便的檔案路徑字串處理,而 Path 是 PurePath 加上 System Call 操作,也就是說,需要用 Path 才能與作業系統互動,包括檢查檔案是否存在、讀寫檔案、查看使用者家目錄等功能,總之,大多時候就用 Path 吧!

# PurePath 與 Path 差別範例
## 當你正在使用 Mac 或 Linux
## 你還是可以直接創造一個 Windows 的 PurePath 物件
>>> pathlib.PureWindowsPath()
PureWindowsPath('.')
## 但是不能創造 WindowsPath 物件
## 因為作業系統不同、無法呼叫 Windows 的 System Call
>>> pathlib.WindowsPath()
NotImplementedError: cannot instantiate 'WindowsPath' on your system

Path 的類別方法

有些檔案路徑操作不需要你先創造 Path 物件,用類別方法(class method)就能直接呼叫。

如果你對類別方法不是很理解,好豪在 這則筆記的 4-6 小節 記下了值得一看的教學文章。

## 目前所在路徑(Current Working Directory)
>>> Path.cwd()
PosixPath('/Users/haohao/Desktop')
## 使用者的家目錄路徑
>>> Path.home()
PosixPath('/Users/haohao')

## 因為是 classmethod,所以從已創造的 Path 物件呼叫也可以
>>> p = Path()
>>> p.cwd()
PosixPath('/Users/haohao/Desktop')
>>> p.home()
PosixPath('/Users/haohao')

好豪自己覺得容易忘記的函式關鍵字

刪除:unlink()

檔案的移除常見函式關鍵字是 remove,例如 os.remove()。在 Path 的刪除則是使用 unlink()、與 Linux 指令相似,請參考上方 檔案讀寫範例

所在資料夾:.parent

要查詢檔案或路徑所在的資料夾,直覺想到關鍵字會用 dir,例如 os.path.dirname()。Path 的關鍵字是用 parent,並且不是用函式、而是使用屬性(property),請看上方 用到 .parent 的範例

如果你原本常用 os.path ,想跳來用 pathlib.Path

pathlib 官方文件的頁面尾端,貼心地提供了 os.path 與 pathlib.Path 的函式功能對應表格,推薦你閱讀、可以快速上手 Path 操作。

fastai 的 Path 使用

fastai 是建立在 Pytorch 之上的高階 API,就像大家愛用 Keras 簡化 Tensorflow 操作一樣,fastai 致力於讓基於 Pytorch 的深度學習流程更簡單。而 fastai 就用了 Path 來讓檔案操作更有效率,以下會列舉兩個例子。

ls

在 Linux 指令中超常用的指令,ls 會列出當下路徑內所有檔案與資料夾,Path 可以用 iterdir() 達成相似的功能。

fastai 用 Monkey Patch 的方式,直接在 Path 類別加上 ls 函式,筆者好豪在下方以簡單版的實現方式介紹:

## ls 函式在 Path 類別的 Monkey Patch
>>> Path.ls = lambda x: [o.name for o in x.iterdir()]
>>> p = Path()
>>> p.ls()
['test_subdir', 'test_2.txt', 'test_3.txt', 'test_1.txt']

## 使用 is_file() 或 is_dir(),只列出檔案、或只列出資料夾
>>> [o.name for o in p.iterdir() if o.is_dir()]
['test_subdir']

fastai ls() source code

untar_data

fastai 內 untar_data() 的主要功能是下載並讀取資料,它會將資料存放成 Path 物件、而不是直接讀取檔案。以機器學習會用到的圖片資料集為例,常見存放方式是同個資料夾存放同一個標記(Label)的圖片、或者檔案名稱就是圖片內容標記等等。因此,untar_data() 如此設計,就可以用 Path 方便的路徑字串處理,快速取出標記。以下是 fastai 官方範例

>>> from fastai.vision import untar_data
>>> path = untar_data(URLs.PETS); path
PosixPath('/tmp/.fastai/data/oxford-iiit-pet')
>>> path.ls()
[PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images'),
 PosixPath('/tmp/.fastai/data/oxford-iiit-pet/annotations')]
>>> path_img = path/'images'
>>> fnames = get_image_files(path_img)
>>> fnames[:5]
[PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images/japanese_chin_17.jpg'),
 PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images/japanese_chin_175.jpg'),
 PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images/shiba_inu_156.jpg'),
 PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images/english_cocker_spaniel_142.jpg'),
 PosixPath('/tmp/.fastai/data/oxford-iiit-pet/images/Siamese_47.jpg')]
>>> fnames[0].stem # 從檔名獲得標記
'japanese_chin_17'

fastai untar_data doc

結語

pathlib.Path 用物件導向的方式,提供了方便的檔案路徑字串處理、以及簡易的檔案操作,並且 Path 的路徑處理在不同作業系統都能運作。當你的 Python 程式需要任何檔案路徑相關的處理,就快拿 Path 來用用看吧。


還想知道更多 Python 相關技巧嗎?推薦你閱讀我寫過的更多 Python 教學文章,學會更多 Pythonic Code:

如果這篇文章有幫助到你,歡迎追蹤 好豪的粉絲專頁,我會持續分享 Python 技巧、資料科學等知識;也歡迎點選下方按鈕將本文加入書籤、或者分享給更多正在學 Python 的朋友。

推薦閱讀