之前如果要使用 python 操作文件路徑,我總是會條件反射導入 os.path。 而現在,我會更加喜歡用新式的 pathlib, 雖然用得還是沒有 os.path 熟練,但是以后會堅持使用。
pathlib 庫從 python3.4 開始,到 python3.6 已經比較成熟。如果你的新項目可以直接用 3.6 以上,建議用 pathlib。相比於老式的 os.path 有幾個優勢:
- 老的路徑操作函數管理比較混亂,有的是導入 os, 有的又是在 os.path 當中,而新的用法統一可以用 pathlib 管理。
- 老用法在處理不同操作系統 win,mac 以及 linux 之間很吃力。換了操作系統常常要改代碼,還經常需要進行一些額外操作。
- 老用法主要是函數形式,返回的數據類型通常是字符串。但是路徑和字符串並不等價,所以在使用 os 操作路徑的時候常常還要引入其他類庫協助操作。新用法是面向對象,處理起來更靈活方便。
- pathlib 簡化了很多操作,用起來更輕松。
舉個例子, 把所有的 txt 文本全部移動到 archive 目錄當中(archive 目錄必須存在)。
image.png
使用原來的用法:
import glob
import os
import shutil
# 獲取運行目錄下所有的 txt 文件。注意:不是這個文件目錄下
print(glob.glob('*.txt'))
for file_name in glob.glob('*.txt'):
new_path = os.path.join('archive', file_name)
shutil.move(file_name, new_path)
新的寫法:
from pathlib import Path
Path("demo.txt").replace('archive/demo.txt')
這篇文章列舉了 pathlib 的主要用法,細節知識有點多,我只會介紹 pathlib 的用法,不會每個都列舉和 os 的區別。但是我會在文章最后放上主要操作使用 pathlib 和 os 的對比圖。
最主要的路徑操作,你可以參考國外一位大神總結的圖:
pathlib_cheatsheet.png
1 路徑獲取
- 獲取當前工作目錄
>>> import pathlib
>>> pathlib.Path.cwd()
C:\Users\me\study
# WindowsPath('C:\Users\me\study')
雖然在這里打印出來的很像一個字符串,但實際上得到的是一個 WindowsPath('C:\Users\me\study')
對象。顯示內容由 Path 類的 __repr__
定義。
注意
工作目錄是在哪個目錄下運行你的程序,不是項目目錄。
如果你只想得到字符串表示,不想要 WindowsPath 對象,可以用 str() 轉化:
>>> str(pathlib.Path.cwd())
C:\Users\me\study
- 獲取用戶 home 目錄。
下面的例子因為基本都是使用 pathlib 下面的 Path 類,所以可以換一種導入方式。
from pathlib import Path
>>> Path.home()
c:\Users\me
- 獲取當前文件路徑
>>> Path(__file__)
demo_01.py
在 pycharm 中右擊運行和在 cmd 運行的結果會不同。pycharm 會顯示全路徑,cmd 運行只會顯示工作目錄下的相對路徑。如果想統一,可以添加后綴 .resolve()
轉化成絕對路徑,這個在后面還會提到。
image.png
- 獲取任意字符串路徑
>>> Path('subdir/demo_02.py')
subdir\demo_02.py
>>> Path('c:d:y/rad.txt')
c:d:y\rad.txt
這里需要注意 2 點:
- 不管字符串使用的是正斜杠
/
還是反斜杠\
, 在 windows 系統里,得到的路徑都是反斜杠\
, pathlib 會根據操作系統智能處理。 - 第二個例子中字符串會被
/
分割,c:d:y
會被當做一個目錄名字,pathlib 不會去判斷這個文件真的存在哦。
- 獲取絕對路徑
只需要在任意路徑對象后添加方法 .resolve()
就能獲取路徑的絕對路徑。如果填入的路徑是相對路徑(windows 下沒有盤符,linux 沒有 / 開頭),則會在當前工作目錄后添加路徑。如果是已經是絕對路徑,則只會根據操作系統優化表達。
>>> file = Path('archive/demo.txt')
>>> file
archive\demo.txt
>>> file.resolve()
C:\Users\me\study\archive\demo.txt
- 獲取文件屬性
文件屬性比如文件大小,創建時間,修改時間等等。
file = Path('archive/demo.txt')
print(file.stat())
print(file.stat().st_size)
print(file.stat().st_atime)
print(file.stat().st_ctime)
print(file.stat().st_mtime)
找出最后修改的文件的例子:
>>> path = Path.cwd()
>>> max(
[(f.stat().st_mtime, f)
for f in path.iterdir()
if f.is_file()]
)
1589171135.860173 C:\Users\me\study\demo_03.py
2 路徑組成部分
獲取路徑的組成部分非常方便:
- .name 文件名,包含后綴名,如果是目錄則獲取目錄名。
- .stem 文件名,不包含后綴。
- .suffix 后綴,比如
.txt
,.png
。 - .parent 父級目錄,相當於
cd ..
- .anchor 錨,目錄前面的部分
C:\
或者/
。
>>> file = Path('archive/demo.txt')
>>> file.name
demo.txt
>>> file.stem
demo
>>> file.suffix
.txt
>>> file.parent
C:\Users\me\study\archive
>>> file.anchor
'C:\'
- 獲取上一級目錄
>>> file = Path('archive/demo.txt')
>>> file.parent
archive
獲取上 2 級 和 3 級目錄會有點問題。因為傳入的是一個相對路徑,上 3 級已經無法往上了,所以還是會停留在工作目錄。所以你需要確保你的路徑有這么多層級。
>>> file.parent.parent
.
>>> file.parent.parent.parent
.
- 獲取所有的上級目錄:
>>> file.parents
<WindowsPath.parents>
>>> list(file.parents)
[WindowsPath('archive'), WindowsPath('.')]
- 父級目錄的另一種表示方法:
>>> file.parents[0]
archive
如果路徑是在當前工作目錄下的子目錄,最好轉化成絕對路徑再獲取上層目錄:
>>> file.resolve().parents[4]
C:\Users
# 不能使用負數
在 os 模塊中獲取上 3 級目錄簡直令人奔潰,需要重復使用 dirname 函數,使用 pathlib 的 parent 可以極大簡單化操作:
>>> import os
>>> os.path.dirname(file)
- 相對其他某個路徑的結果:
>>> file.relative_to('archive')
dmeo.txt
3 子路徑掃描
- dir_path.iterdir() 可以掃描某個目錄下的所有路徑(文件和子目錄), 打印的會是處理過的絕對路徑。
>>> cwd = Path.cwd()
>>> [path for path in cwd.iterdir() if cwd.is_dir()]
[
WindowsPath('C:/Users/me/study/archive'),
WindowsPath('C:/Users/me/study/demo_01.py'),
WindowsPath('C:/Users/me/study/new_archive')
]
- 使用 iterdir() 可以統計目錄下的不同文件類型:
>>> path = Path.cwd()
>>> files = [f.suffix for f in path.iterdir() if f.is_file()]
>>> collections.Counter(files)
Counter({'.py': 3, '.txt': 1})
- 查找目錄下的指定文件 glob 。
使用模式匹配(正則表達式)匹配指定的路徑。正則表達式不熟練的可以查看 這個教程,真的讓我學會了正則表達式。glob 只會匹配當前目錄下, rglob 會遞歸所有子目錄。下面這個例子,demo.txt 在 archive 子目錄下。所以用 glob 找到的是空列表,rglob 可以找到。glob 得到的是一個生成器,可以通過 list() 轉化成列表。
>>> cwd = Path.cwd()
>>> list(cwd.glob('*.txt'))
[]
>>> list(cwd.rglob('*.txt'))
[WindowsPath('C:/Users/me/study/archive/demo.txt')]
- 檢查路徑是否符合規則 match
>>> file = Path('/archive/demo.txt')
>>> file.match('*.txt')
True
4 路徑拼接
pathlib 支持用 /
拼接路徑。熟悉魔術方法的同學應該很容易理解其中的原理。
>>> Path.home() / 'dir' / 'file.txt'
C:\Users\me\dir\file.txt
如果用不慣 /
,也可以用類似 os.path.join 的方法:
>>> Path.home().joinpath('dir', 'file.txt')
C:\Users\me\dir\file.txt
5 路徑測試(判斷)
- 是否為文件
>>> Path("archive/demo.txt").is_file()
True
>>> Path("archive/demo.txt").parent.is_file()
False
- 是否為文件夾 (目錄)
>>> Path("archive/demo.txt").is_dir()
False
>>> Path("archive/demo.txt").parent.is_dir()
True
- 是否存在
>>> Path("archive/demo.txt").exists
True
>>> Path("archive/dem.txt").exists
False
6 文件操作
- 創建文件 touch
>>> file = Path('hello.txt')
>>> file.touch(exist_ok=True)
None
>>> file.touch(exist_ok=False)
FileExistsError: [Errno 17] File exists: 'hello.txt'
exist_ok 表示當文件已經存在時,程序的反應。如果為 True,文件存在時,不進行任何操作。如果為 False, 則會報 FileExistsError
錯誤。
- 創建目錄 path.mkdir
用 os 創建目錄分為 2 個函數 : mkdir()
和 makedirs()
。 mkdir()
一次只能創建一級目錄, makedirs()
可以同時創建多級目錄:
>>> os.mkdir('dir/subdir/3dir')
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: 'dir/subdir/3dir'
>>> os.makedirs('dir/subdir/3dir')
None
使用 pathlib 只需要用 path.mkdir() 函數就可以。它提供了 parents 參數,設置為 True 可以創建多級目錄;不設置則只能創建 一層:
>>> path = Path('/dir/subdir/3dir')
>>> path.mkdir()
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: 'dir/subdir/3dir'
>>> path.mkdir(parents=True)
None
- 刪除目錄 path.rmdir()
刪除目錄非常危險,並且沒有提示,一定要謹慎操作。一次只刪除一級目錄,且當前目錄必須為空。
>>> path = Path('dir/subdir/3dir')
>>> path.rmdir()
None
- 刪除文件 path.unlink, 危險操作。
>>> path = Path('archive/demo.txt')
>>> path.unlink()
- 打開文件
使用 open() 函數打開文件時,如果需要傳入文件路徑。可以用字符串作為參數傳入:
with open('archive/demo.txt') as f:
print(f.read())
也可以傳入 Path 對象:
file_path = Path('archive/demo.txt')
with open(file_path) as f:
print(f.read())
如果經常使用 pathlib,可以在獲取到 Path 路徑以后直接調用 path.open() 方法。至於到底用哪一個,其實不必太在意,因為 path.open() 也是調用內置函數 open()。
file = Path('archive/demo.txt')
with file.open() as f:
print(f.read())
不過 pathlib 對讀取和寫入進行了簡單的封裝,不再需要重復去打開文件和管理文件的關閉了。
- .read_text() 讀取文本
- .read_bytes() 讀取 bytes
- .write_text() 寫入文本
- .write_bytes() 寫入 tytes
>>> file_path = Path('archive/demo.txt')
>>> file_path.read_text() # 讀取文本
'text in the demo.txt'
>>> file_path.read_bytes() # 讀取 bytes
b'text in the demo.txt'
>>> file.write_text('new words') # 寫入文本
9
>>> file.write_bytes(b'new words') # 寫入 bytes
9
注意
file.write 操作使用的是 w 模式,如果之前已經有文件內容,將會被覆蓋。
- 移動文件
txt_path = Path('archive/demo.txt')
res = txt_path.replace('new_demo.txt')
print(res)
這個操作會把 archive 目錄下的 demo.txt 文件移動到當前工作目錄,並重命名為 new_demo.txt。
移動操作支持的功能很受限。比如當前工作目錄如果已經有一個 new_demo.txt 的文件,則里面的內容都會被覆蓋。還有,如果需要移動到其他目錄下,則該目錄必須要存在,否則會報錯:
# new_archive 目錄必須存在,否則會報錯
txt_path = Path('archive/demo.txt')
res = txt_path.replace('new_archive/new_demo.txt')
print(res)
為了避免出現同名文件里的內容被覆蓋,通常需要進行額外處理。比如判斷同名文件不能存在,但是父級目錄必須存在;或者判斷父級目錄不存在時,創建該目錄。
dest = Path('new_demo.txt')
if (not dest.exists()) and dest.parent.exists():
txt_path.replace(dest)
- 重命名文件
txt_path = Path('archive/demo.txt')
new_file = txt_path.with_name('new.txt')
txt_path.replace(new_file)
- 修改后綴名
txt_path = Path('archive/demo.txt')
new_file = txt_path.with_suffix('.json')
txt_path.replace(new_file)
注意
不管是移動文件還是刪除文件,都不會給任何提示。所以在進行此類操作的時候要特別小心。
對文件進行操作最好還是用 shutil 模塊。
7 附件:常用的 pathlib 和 os 對比圖
操作 | os and os.path | pathlib |
---|---|---|
絕對路徑 | os.path.abspath |
Path.resolve |
修改權限 | os.chmod |
Path.chmod |
創建目錄 | os.mkdir |
Path.mkdir |
重命名 | os.rename |
Path.rename |
移動 | os.replace |
Path.replace |
刪除目錄 | os.rmdir |
Path.rmdir |
刪除文件 | os.remove , os.unlink |
Path.unlink |
工作目錄 | os.getcwd |
Path.cwd |
是否存在 | os.path.exists |
Path.exists |
用戶目錄 | os.path.expanduser |
Path.expanduser and Path.home |
是否為目錄 | os.path.isdir |
Path.is_dir |
是否為文件 | os.path.isfile |
Path.is_file |
是否為連接 | os.path.islink |
Path.is_symlink |
文件屬性 | os.stat |
Path.stat , Path.owner , Path.group |
是否為絕對路徑 | os.path.isabs |
PurePath.is_absolute |
路徑拼接 | os.path.join |
PurePath.joinpath |
文件名 | os.path.basename |
PurePath.name |
上級目錄 | os.path.dirname |
PurePath.parent |
同名文件 | os.path.samefile |
Path.samefile |
后綴 | os.path.splitext |
PurePath.suffix |
參考文獻
-
[Python3 中使用 Pathlib 模塊進行文件操作](https://cuiqingcai.com/6598.html)
更多原創文章,請聯系公眾號:手邊字節