python路徑操作新標准:pathlib 模塊


之前如果要使用 python 操作文件路徑,我總是會條件反射導入 os.path。 而現在,我會更加喜歡用新式的 pathlib, 雖然用得還是沒有 os.path 熟練,但是以后會堅持使用。

pathlib 庫從 python3.4 開始,到 python3.6 已經比較成熟。如果你的新項目可以直接用 3.6 以上,建議用 pathlib。相比於老式的 os.path 有幾個優勢:

  1. 老的路徑操作函數管理比較混亂,有的是導入 os, 有的又是在 os.path 當中,而新的用法統一可以用 pathlib 管理。
  2. 老用法在處理不同操作系統 win,mac 以及 linux 之間很吃力。換了操作系統常常要改代碼,還經常需要進行一些額外操作。
  3. 老用法主要是函數形式,返回的數據類型通常是字符串。但是路徑和字符串並不等價,所以在使用 os 操作路徑的時候常常還要引入其他類庫協助操作。新用法是面向對象,處理起來更靈活方便。
  4. pathlib 簡化了很多操作,用起來更輕松。

舉個例子, 把所有的 txt 文本全部移動到 archive 目錄當中(archive 目錄必須存在)。

image.pngimage.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.pngpathlib_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.pngimage.png

  • 獲取任意字符串路徑
>>> Path('subdir/demo_02.py')
subdir\demo_02.py
>>> Path('c:d:y/rad.txt')
c:d:y\rad.txt

這里需要注意 2 點:

  1. 不管字符串使用的是正斜杠 / 還是反斜杠 \, 在 windows 系統里,得到的路徑都是反斜杠\, pathlib 會根據操作系統智能處理。
  2. 第二個例子中字符串會被 / 分割,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

參考文獻

更多原創文章,請聯系公眾號:手邊字節


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM