Python的pathlib模塊:駕馭文件系統(tǒng)

原文鏈接:https://realpython.com/python-pathlib/#creating-empty-files
by
Apr 17, 2023
目錄
將路徑表示為字符串的隱患
用Python的pathlib把路徑實(shí)例化
使用Path類的方法
傳入字符串
拼接路徑
用Path進(jìn)行文件系統(tǒng)操作
獲取路徑的各個(gè)部分
讀寫文件
重命名文件
復(fù)制文件
移動(dòng)、刪除文件
創(chuàng)建空文件
Python pathlib案例
統(tǒng)計(jì)文件個(gè)數(shù)
顯示文件夾樹狀結(jié)構(gòu)
找到最近修改的文件
創(chuàng)建獨(dú)一無(wú)二的文件名
結(jié)語(yǔ)
對(duì)Python開(kāi)發(fā)者來(lái)說(shuō),和文件打交道、和文件系統(tǒng)互動(dòng)都是稀疏平常的事。有時(shí)是僅僅讀寫文件,而有時(shí)則更復(fù)雜。也許你需要在一個(gè)給定文件夾里列出給定類別的所有文件、找到給定文件的父級(jí)文件夾或是創(chuàng)建一個(gè)先前不存在的獨(dú)一無(wú)二的文件名。這時(shí)就可以用 pathlib
。
pathlib
模塊在Python標(biāo)準(zhǔn)庫(kù)里,能幫助你應(yīng)對(duì)上述挑戰(zhàn)。它集成了諸多必要功能,你可以方便地調(diào)用 Path
對(duì)象的方法和屬性來(lái)達(dá)成這些功能。
在本教程中,你將學(xué)到:
在Python里處理文件和文件夾的路徑
以多種方式實(shí)例化一個(gè)
Path
對(duì)象使用
pathlib
來(lái)讀寫文件謹(jǐn)慎地復(fù)制、移動(dòng)和刪除文件
操作路徑和底層文件系統(tǒng)
獲取路徑的各個(gè)部分
你也將在本教程里學(xué)到一大堆代碼案例,你完全可以把它們用在你的日常文件操作中。比如,你將深入了解統(tǒng)計(jì)文件個(gè)數(shù)、找到文件夾中最近修改的文件以及創(chuàng)建獨(dú)一無(wú)二的文件名。
pathlib
能提供這么多方法和屬性是極好的,但是匆匆忙忙地背下來(lái)卻很困難。這時(shí)一個(gè)小抄就顯得很有必要啦,點(diǎn)擊下方鏈接獲取你的小抄:
Free Download: so you can tame the file system with Python.
將路徑表示為字符串的隱患
有了Python的 pathlib
,你終于不用那么頭疼了。它靈活的 Path
類有著直觀的語(yǔ)義。先別急著了解這個(gè)類,看看 pathlib
誕生前,Python開(kāi)發(fā)者們是怎么處理路徑的。
傳統(tǒng)方式是,Python用普通的
(文本字符串)來(lái)表示文件路徑。然而,由于路徑所代表的含義不僅僅是一個(gè)字符串,與之相關(guān)的重要功能就在標(biāo)準(zhǔn)庫(kù)里散得到處都是,包括 , , 和 。比如,下面這個(gè)代碼塊將文件移動(dòng)在一個(gè)子文件夾里:
就為了把所有的文本文件移動(dòng)到一個(gè)歸檔文件夾里,你需要三個(gè)
(import 語(yǔ)句)。Python的 pathlib
提供了一個(gè) Path
類,在不同的操作系統(tǒng)里用法都相同。現(xiàn)在你不用導(dǎo)入多個(gè)不同的模塊例如glob
, ?os
, 和 shutil
, pathlib
就夠了:
和第一個(gè)例子一樣,這段代碼找到當(dāng)前文件夾下的文本文件,然后移動(dòng)到一個(gè) archive/
子文件夾下。然而,有了 pathlib
你用很少的 import
語(yǔ)句和直觀的語(yǔ)法就能完成同樣的目的,在接下來(lái)的章節(jié)里你會(huì)學(xué)到更多。
用Python的pathlib把路徑實(shí)例化
pathlib
的初衷之一就是用專門的對(duì)象來(lái)表示文件系統(tǒng), (而不是字符串)。確切地說(shuō), (pathlib
的官方文檔)稱之為面向?qū)ο蟮奈募到y(tǒng)路徑(Object-oriented filesystem paths)
。
當(dāng)你把 pathlib
的語(yǔ)法和老的 os.path
的方式相比較時(shí), (面向?qū)ο蟮姆绞剑┦呛苊黠@的。當(dāng)你注意到pathlib
的核心是 Path
類時(shí),這就更明顯了:
如果你從來(lái)沒(méi)用過(guò)這個(gè)模塊或者不知道用哪個(gè)類,基本用
Path
就對(duì)了。 ( )
事實(shí)上,Path
太常用了以至于你一般可以直接把它導(dǎo)入進(jìn)來(lái):
由于你基本上都在跟 pathlib
中的 Path
類打交道,這種導(dǎo)入 Path
的方式能讓你在碼代碼時(shí)省些功夫。這樣的話,你可以直接使用 Path
,而不是將 pathlib
整個(gè)模塊導(dǎo)入,然后用 pathlib.Path
。
還有些別的方式實(shí)例化一個(gè) Path
對(duì)象。在這個(gè)章節(jié)里,你會(huì)學(xué)習(xí)如何通過(guò) (類方法)、傳入字符串或是拼接路徑來(lái)創(chuàng)建路徑。
使用Path類的方法
你導(dǎo)入 Path
之后,就能用現(xiàn)有的方法來(lái)獲取當(dāng)前工作路徑或是home路徑。
當(dāng)前工作路徑是文件系統(tǒng)里當(dāng)前進(jìn)程所處的路徑。你編程時(shí),比如說(shuō),想要?jiǎng)?chuàng)建或打開(kāi)一個(gè)跟你正在執(zhí)行的腳本同路徑的文件,就需要確定當(dāng)前工作路徑。
此外,跟文件打交道時(shí)知道用戶home路徑也很有用。將home路徑作為起點(diǎn),你就可以在不同的機(jī)器(操作系統(tǒng))里指定任意路徑,不論用戶名是什么。
為了獲取當(dāng)前工作路徑,你可以使用 .cwd()
:
當(dāng)你實(shí)例化 pathlib.Path
,你得到一個(gè) WindowsPath
或是 PosixPath
對(duì)象,取決于你正在使用的操作系統(tǒng)。
在Windows里,.cwd()
返回一個(gè) WindowsPath
。在Linux和macOS里,你得到一個(gè) PosixPath
。盡管內(nèi)部細(xì)節(jié)不同,這些對(duì)象給你提供了完全一致的接口。
操作系統(tǒng)差異
可能你會(huì)顯式地要求一個(gè)
WindowsPath
或PosixPath
,但這樣除了讓你的代碼受限于操作系統(tǒng)類型之外沒(méi)有任何好處。一個(gè)下面這樣的具體路徑在別的操作系統(tǒng)就不起作用了:
當(dāng)如果你就想在Windows機(jī)器上操作Unix路徑,或是反過(guò)來(lái)呢?這樣的話,你可以在任何系統(tǒng)上直接實(shí)例化
PureWindowsPath
或是PurePosixPath
。當(dāng)你這樣來(lái)創(chuàng)建路徑,在內(nèi)部就創(chuàng)建了一個(gè) 。如果你需要表示一個(gè)路徑而不去訪問(wèn)底層文件系統(tǒng),就可以使用這樣的對(duì)象。
一般地,使用 Path
是不錯(cuò)的。有了 Path
,你就可以實(shí)例化一個(gè)當(dāng)前操作系統(tǒng)的具體的路徑,而你的代碼又不會(huì)受操作系統(tǒng)限制。具體的路徑允許你對(duì)Path
對(duì)象進(jìn)行系統(tǒng)級(jí)的調(diào)用,但是純路徑(pure path)只允許你操作路徑,而不能訪問(wèn)。
使用不依賴操作系統(tǒng)的路徑意味著你在Windows操作系統(tǒng)上寫進(jìn)腳本里的 Path.cwd()
在macOS或Linux上也能運(yùn)行。當(dāng)然,這一點(diǎn)對(duì) .home()
也適用:
有了 Path.cwd()
和 Path.home()
,你在Python腳本里就可以很方便地獲取到一個(gè)出發(fā)點(diǎn)。如果你需要把路徑拼出來(lái),或是引用子目錄結(jié)構(gòu),就可以通過(guò)字符串實(shí)例化 Path
。
傳入一個(gè)字符串
除了以home路徑或工作路徑為出發(fā)點(diǎn),你還可以傳入字符串來(lái)指向它代表的路徑:
這些步驟創(chuàng)建了一個(gè) Path
對(duì)象。你不用處理字符串,相反,可以用 pathlib
提供的更靈活的方案。
在Windows上,路徑分隔符是反斜杠(\)。然而,在許多情況下,反斜杠也被用作 (轉(zhuǎn)義字符),用來(lái)代表不可打印的字符。為了避免出問(wèn)題,使用原始字符串字面量來(lái)表示W(wǎng)indows路徑:
以 r
開(kāi)頭的字符串是原始字符串字面量。在原始字符串字面量里, \ 代表了一個(gè)字面量反斜杠。在普通字符串里,你需要使用兩個(gè)反斜杠(\ \)來(lái)表明你希望使用字面量反斜杠而不是轉(zhuǎn)義字符。
注意:一種獲取當(dāng)前模塊工作路徑的地道方式是使用
__file__
:
(
__file__
屬性)包含了Python當(dāng)前導(dǎo)入或是運(yùn)行的文件路徑。如果需要操作模塊本身的路徑,你可以把__file__
傳給Path
。比如,也許你想用.parent
獲取父級(jí)路徑。
你也許已經(jīng)注意到了,即使在Windows里你輸入路徑時(shí)用的反斜杠, pathlib
也會(huì)在表示路徑時(shí)用斜杠(/)作為路徑分隔符。這種表示方式稱作POSIX風(fēng)格。
POSIX是
的縮寫,是一種兼容不同操作系統(tǒng)的標(biāo)準(zhǔn)。這個(gè)標(biāo)準(zhǔn)的內(nèi)容遠(yuǎn)不止路徑的表示法。你在 里能了解到更多。同樣地,當(dāng)你把一個(gè)Path
對(duì)象轉(zhuǎn)回字符串,它也會(huì)回到本地形式——例如,Windows里就是反斜杠:
通常來(lái)說(shuō),為了方便,你的代碼里應(yīng)該盡可能使用 Path
對(duì)象,但是在特定情況下轉(zhuǎn)回字符串也是必要的。一些庫(kù)和API仍然希望你傳入字符串形式的文件路徑,此時(shí)你就需要在傳入特定的函數(shù)前,先把 Path
對(duì)象轉(zhuǎn)成字符串。
拼接路徑
第三種構(gòu)造Path
對(duì)象的方法是用斜杠來(lái)連接路徑的各個(gè)部分,這可能是 pathlib
庫(kù)里最不常用的功能。你可能在本教程剛開(kāi)始的例子中就已經(jīng)吃驚過(guò)一次了:
正斜杠運(yùn)算符可以連接多個(gè)路徑或是連接路徑和字符串,只要這中間有一個(gè)Path
對(duì)象就行。不管你的操作系統(tǒng)實(shí)際用什么作為路徑分隔符,這里都用正斜杠。
如果你不喜歡這種特殊的正斜杠符號(hào),也可以用 .joinpath()
方法來(lái)做到同樣的事:
這種方式更接近你以前可能用過(guò)的 .joinpath()
是一種對(duì)你來(lái)說(shuō)更熟悉的方式,而不是正斜杠。
在你實(shí)例化 Path
之后,你可能想對(duì)它做點(diǎn)什么。比如,也許你想執(zhí)行文件操作,或是從路徑里截取一些部分。這就是你接下來(lái)要做的。
用Path進(jìn)行文件系統(tǒng)操作
通過(guò)使用 pathlib
,你可以方便地 (對(duì)文件系統(tǒng))做一大堆操作。在這個(gè)章節(jié),你將對(duì)一些最常用的功能有一個(gè)寬泛概念。但是在你開(kāi)始操作文件之前,看看一個(gè)路徑有哪些部分。
獲取路徑的各個(gè)部分
一個(gè)文件或文件夾路徑由不同部分組成。當(dāng)你使用 pathlib
,這些部分以屬性的形式方便地供你獲取。基本有這些:
.name
:不含文件夾的純文件名.stem
:不含后綴的文件名.suffix
:文件后綴名.anchor
:各級(jí)文件夾之前的部分.parent
:包含了該路徑的父級(jí)文件夾
這里,你可以上手觀察這些屬性:
Windows:
Linux:
macOS:
注意 .parent
返回的是新的 Path
對(duì)象,而其他屬性返回的是字符串。這就是說(shuō),例如,你可以像最后一個(gè)例子那樣連著用 .parent
,甚至再跟正斜杠組合使用來(lái)創(chuàng)造全新的路徑:
要記的屬性相當(dāng)多。如果你想在引用這些 Path
屬性方便一點(diǎn),可以點(diǎn)這個(gè)鏈接下載Real Python的pathlib
小抄:
Free Download: so you can tame the file system with Python.
讀寫文件
試想一下,你想 shoppping_list.md
長(zhǎng)這樣:
傳統(tǒng)做法來(lái)說(shuō), open()
函數(shù)。有了 pathlib
,你可以直接對(duì)Path
對(duì)象使用 open()
。
所以,這個(gè)找到并打印shopping_list.md
中物件的腳本的第一份草稿長(zhǎng)這樣:
事實(shí)上, Path.open()
底層就是在調(diào)用 (內(nèi)置open()
)函數(shù)。這就是為什么你在 Path.open()
里可以使用 mode
、encoding
這些參數(shù)。
除此之外, pathlib
提供了一些便于讀寫文件的方法:
.read_text()
以文本模式打開(kāi)路徑并返回字符串格式的內(nèi)容。.read_bytes()
以二進(jìn)制模式打開(kāi)路徑并返回字節(jié)字符串的內(nèi)容。.write_text()
打開(kāi)路徑并寫入字符串?dāng)?shù)據(jù)。.write_bytes()
以二進(jìn)制模式打開(kāi)路徑并寫入數(shù)據(jù)。
上面每個(gè)方法都能打開(kāi)并關(guān)閉文件。因此,你可以用 .read_text()
更新?
如果你想創(chuàng)建一個(gè)只包含了雜貨的購(gòu)物清單,可以以相似的方式使用 .write_text()
:
當(dāng)使用 .write_text()
時(shí),Python會(huì)在沒(méi)有提示的情況下覆寫同路徑的文件。這就是說(shuō)你一步操作就會(huì)讓所有辛苦工作付之東流!一如既往地,當(dāng)你在Python里寫入文件時(shí),對(duì)代碼正在做的事應(yīng)該很謹(jǐn)慎,重命名時(shí)也一樣。
重命名文件
當(dāng)你希望重命名文件,你可以使用 .with_stem()
,.with_suffix()
, 或者 .with_name()
。這些方法會(huì)返回原始路徑,但替換了文件名/文件后綴/或兩者:
使用 .with_suffix()
返回了一個(gè)新的路徑,接下來(lái)你得使用 .replace()
才能實(shí)實(shí)在在地重命名文件。這會(huì)把 txt_path
移動(dòng)到 md_path
然后在保存時(shí)重命名。
如果你想改變整個(gè)文件名,包括后綴,那么你可以用 .with_name()
:
上面這段代碼把 hello.txt
改成了 goodbye.md
。
如果你只想改變文件名,不想改后綴,就可以用 .with_stem
。你會(huì)在下一個(gè)章節(jié)學(xué)習(xí)這個(gè)方法。
復(fù)制文件
令人驚訝的是,Path
沒(méi)有提供任何復(fù)制文件的方法。但是有了前面學(xué)到的 pathlib
的知識(shí),你可以用較少的代碼創(chuàng)造這個(gè)功能:
你使用 .with_stem()
在不改變后綴的情況下創(chuàng)建新的文件名。實(shí)際的復(fù)制行為發(fā)生在最后一行,你使用 .read_bytes()
來(lái)讀取 source
的內(nèi)容,然后使用 .write_bytes()
把內(nèi)容寫入 destination
。
雖然使用 pathlib
來(lái)處理所有跟路徑相關(guān)的事具有誘惑力,但你確實(shí)也會(huì)想 (使用shutil
來(lái)復(fù)制文件)。用 Path
對(duì)象來(lái)復(fù)制文件也是個(gè)不錯(cuò)的選擇。
移動(dòng)、刪除文件
通過(guò) pathlib
,你也可以進(jìn)行基礎(chǔ)系統(tǒng)級(jí)別的文件操作,例如移動(dòng)、更新,甚至刪除文件。大多數(shù)情況下,這些方法在刪除信息、文件前都沒(méi)有警告或是等待確認(rèn)。所以,使用的時(shí)候要小心。
要移動(dòng)一個(gè)文件,你可以使用 .replace()
。注意如果目標(biāo)路徑已經(jīng)存在,那么 .replace()
會(huì)覆寫它。為了避免可能的覆寫,你可以在移動(dòng)前測(cè)試一下目標(biāo)路徑是否存在:
然而,這并沒(méi)有考慮到可能的 if
語(yǔ)句和 .replace()
方法之間時(shí),往 destination
路徑下添加了一個(gè)文件。如果你擔(dān)心這樣,那么更安全的一種方式是打開(kāi)目標(biāo)路徑時(shí)進(jìn)行 (獨(dú)占創(chuàng)建)然后顯式地復(fù)制源數(shù)據(jù)并在之后刪除源文件:
如果 destination
已經(jīng)存在,那么上述代碼就會(huì)捕獲 FileExistsError
然后打印一個(gè)警告。為了執(zhí)行移動(dòng)操作,你得在復(fù)制結(jié)束后用 unlink()
刪除 source
。使用 else
能確保文件在復(fù)制失敗時(shí)不會(huì)被刪除。
創(chuàng)建空文件
要用 pathlib
創(chuàng)建空文件,你可以使用 .touch()
。這個(gè)方法意在更新文件的修改時(shí)間,但你也可以利用他的副作用來(lái)創(chuàng)建一個(gè)新的文件:
在上面的例子中,你實(shí)例化了一個(gè) Path
對(duì)象然后用 .touch()
創(chuàng)建了文件。你使用了2次 exists()
來(lái)檢查文件事先并不存在,并且在之后被成功創(chuàng)建。如果你再次使用 .touch
,那就會(huì)更新這個(gè)文件的修改時(shí)間。
如果你不想一不小心修改了文件,那么可以使用 exist_ok
參數(shù)并設(shè)成 False
:
當(dāng)你對(duì)一個(gè)不存在的文件路徑使用 .touch()
,可以創(chuàng)建一個(gè)空文件。如果你想先把文件名存下來(lái)待會(huì)兒使用,暫時(shí)還沒(méi)什么需要寫入的,那么用Path.touch()
創(chuàng)建一個(gè)空的文件就有用。例如,你可能想創(chuàng)建一個(gè)空文件來(lái)確保某個(gè)文件名是可用的,即使現(xiàn)在沒(méi)什么內(nèi)容要寫入。
Python pathlib 案例
在這個(gè)章節(jié),你會(huì)看到一些使用 pathlib
解決Python開(kāi)發(fā)者日常挑戰(zhàn)的案例。你可以在你的代碼中,將這些案例作為出發(fā)點(diǎn),或是存下來(lái)之后使用。
統(tǒng)計(jì)文件個(gè)數(shù)
有各種方式來(lái) pathlib
,你可以方便地使用 iterdir()
方法,它會(huì)迭代給定目錄下的所有文件。在下面的例子中,你結(jié)合了 iterdir()
和 來(lái)統(tǒng)計(jì)當(dāng)前目錄下的每種文件的個(gè)數(shù):
你可以用 .glob()
和 .rglob()
方法創(chuàng)建更多靈活的文件列表。例如, Path.cwd().glob(".txt")
返回當(dāng)前目錄下所有含 .txt
后綴的文件。接下來(lái),你只統(tǒng)計(jì)后綴以 p
開(kāi)頭的文件:
如果你想遞歸搜索當(dāng)前目錄及其子目錄下的所有文件,就用 .rglob()
。這個(gè)方法還提供了炫酷的方式來(lái)展示文件夾樹,看下一個(gè)例子。
展示一個(gè)文件夾樹
在這個(gè)例子中,你定義了一個(gè) tree()
函數(shù),會(huì)打印出代表文件結(jié)構(gòu)的可視化樹,以給定的目錄作為根節(jié)點(diǎn)。這通常,舉個(gè)例子,在你想瀏覽一遍項(xiàng)目子文件夾時(shí)很有用。
要遍歷子文件夾, 你使用
:
注意,你需要知道一個(gè)文件的位置離根節(jié)點(diǎn)有多遠(yuǎn)。要做到這點(diǎn),你先是用了 .relative_to()
來(lái)表示與根目錄的相對(duì)路徑。然后,你又用了 .parts
屬性來(lái)統(tǒng)計(jì)路徑表示中的文件夾層級(jí)數(shù)。當(dāng)函數(shù)運(yùn)行時(shí),就會(huì)創(chuàng)建一個(gè)下面這樣的可視化樹:
如果你還想提升這段代碼,那么你可以嘗試創(chuàng)建
(命令行中的目錄樹生成器)。找到最近修改的文件
.iterdir()
, .glob()
和 .rglob()
方法都很適合 (生成器語(yǔ)法)和 (列表推導(dǎo)式)。要找到最近修改的文件,你可以使用 .stat()
方法來(lái)獲取底層文件的信息。例如, .stat.st_mtime
提供了文件的最近修改時(shí)間:
像 .stat().st_mtime
這樣的屬性返回的時(shí)間戳代表從1970年1月1日至今的秒數(shù),也稱作 。如果你喜歡別的格式,可以用 time.localtime
或是 time.ctime
來(lái)把時(shí)間戳轉(zhuǎn)換成更合適的格式。如果上面的例子點(diǎn)燃了你的好奇心,那么你也許想繼續(xù)了解 (在Python里如何獲取并使用當(dāng)前時(shí)間)。
創(chuàng)建獨(dú)一無(wú)二的文件名
在最后這個(gè)例子里,你會(huì)構(gòu)造獨(dú)一無(wú)二的基于一定字符串模板的數(shù)字編號(hào)文件名。如果你不想覆寫已有文件,這就顯得很有用:
在 unique_path()
里,你為文件名指定了一個(gè)模板,內(nèi)部留有計(jì)數(shù)變量的位置。然后,你檢查了組合目錄和文件名之后的新路徑是否存在,文件名里有計(jì)數(shù)變量的值。如果已經(jīng)存在了,就增加計(jì)數(shù)變量,再次嘗試。
現(xiàn)在你可以運(yùn)行上面的代碼來(lái)獲取獨(dú)一無(wú)二的文件名了:
如果目錄下已經(jīng)有 test001.txt
和 test002.txt
,那上面的代碼就會(huì)把 path
設(shè)成 test003.txt
。
結(jié)語(yǔ)
Python的 pathlib
模塊提供了現(xiàn)代化、Pythonic的方式來(lái)處理文件路徑,讓代碼可讀性更強(qiáng)、更容易維護(hù)。有了 pathlib
,你可以用專門的 Path
對(duì)象來(lái)表示文件路徑,而不是純字符串。
在本教程中,你學(xué)到了:
在Python里處理文件和文件夾的路徑
以多種方式實(shí)例化一個(gè)
Path
對(duì)象使用
pathlib
來(lái)讀寫文件謹(jǐn)慎地復(fù)制、移動(dòng)和刪除文件
操作路徑和底層文件系統(tǒng)
獲取路徑的各個(gè)部分
pathlib
模塊通過(guò)提供有用的方法和屬性,讓處理文件路徑更加方便。不同系統(tǒng)的特異性也被 Path
對(duì)象隱藏了起來(lái),使得你的代碼在不同操作系統(tǒng)中能保持一致。
如果你想獲取一個(gè)總結(jié)性的PDF,包含了 pathlib
提供的方便的方法和屬性,可以點(diǎn)擊下方鏈接:
Free Download: so you can tame the file system with Python.
翻譯說(shuō)明:
翻譯原稿為markdown文檔,超鏈接復(fù)制到B站后會(huì)失效。