作者 | Geir Arne Hjelle
譯者 | 陳祥安
來源 | Python學習開發(ID:python3-5)
前言
大多人處理檔案用的最多的還是os模快吧,比如下面這樣的操作:
>>> path.rsplit('\\', maxsplit=1)[0]
或者寫出下面這樣長長的代碼
>>> os.path.isfile(os.path.join(os.path.expanduser('~'), 'realpython.txt'))
在本教程中,你将了解如何使用pathlib子產品操作目錄和檔案的名稱。 學習如何讀取和寫入檔案,拼接路徑和操作底層檔案系統的新方法,以及如何列出檔案并疊代它們的一些示例。 使用pathlib子產品,可以使代碼使用更優雅,可讀和Pythonic代碼重寫上面的兩個示例,如:
>>> path.parent>>> (pathlib.Path.home() / 'realpython.txt').is_file()
Python檔案路徑處理問題
由于許多不同的原因,使用檔案和與檔案系統互動很重要。 最簡單的情況可能隻涉及讀取或寫入檔案,但有時候會有更複雜的任務。 也許你需要列出給定類型的目錄中的所有檔案,查找給定檔案的父目錄,或者建立一個尚不存在的唯一檔案名。
一般情況,Python使用正常文本字元串表示檔案路徑。 一般在使用os,glob和shutil等庫的時候會使用到路徑拼接的操作,使用os子產品拼接起來顯得略顯複雜,以下示例僅需要三個import語句來将所有文本檔案移動到歸檔目錄:
import globimport osimport shutilfor file_name in glob.glob('*.txt'):
new_path = os.path.join('archive', file_name)
shutil.move(file_name, new_path)
使用正常的字元串去拼接路徑是可以的,但是由于不同的作業系統使用的分隔符不同,這樣就容易出現問題,是以一般我們使用最多的還是使用os.path.join()。
Python 3.4中引入了pathlib子產品(PEP 428)再一次的優化了路徑的拼接。使用pathlib庫的Path方法,可以将一個普通的字元串轉換為pathlib.Path對象類型的路徑。
早期,其他軟體包仍然使用字元串作為檔案路徑,但從Python 3.6開始,pathlib子產品在整個标準庫中得到支援,部分原因是由于增加了檔案系統路徑協定。 如果你堅持使用傳統的Python,那麼Python 2也有一個可用的向後移植。
ok,說了那麼多下面讓我們看看pathlib如何在實踐中發揮作用。
建立路徑
這裡我們首先要知道兩個用法,先看代碼:
from pathlib import Path
你真正需要知道的是pathlib.Path類。 建立路徑有幾種不同的方式。 首先,有類方法,如.cwd(目前工作目錄)和.home(使用者的主目錄):
from pathlib import Path
now_path = Path.cwd()
home_path = Path.home()print("目前工作目錄",now_path,type(now_path))print("home目錄",home_path,type(home_path))
輸出内容
目前工作目錄 /Users/chennan/pythonproject/demo
home目錄 /Users/chennan
可以發現路徑格式為pathlib.PosixPath這是在unix系統下的顯示。在不同的系統上顯示的格式也是不一樣,在windows系統會顯示為WindowsPath。但是不管什麼顯示類型,都不影響後面的操作。
前面我們提到過可以通過把字元串類型的路徑,轉換為Pathlib.Path類型的路徑,經過測試發現在Python3.4以後很多子產品以及支援該格式的路徑。不用轉為成字元串使用了。
使用方法如下:
import pathlib
DIR_PATH = pathlib.Path("/Users/chennan/CDM")
print(DIR_PATH,type(DIR_PATH))輸出内容:
輸出内容:
/Users/chennan/CDM <class 'pathlib.PosixPath'>
比起os.path.join拼接路徑的方式,pathlib使用起來更加的友善,使用示例如下:
import pathlib
DIR_PATH = pathlib.Path("/Users/chennan/CDM") / "2000"print(DIR_PATH,type(DIR_PATH))
輸出
/Users/chennan/CDM/2000 <class 'pathlib.PosixPath'>
通過 "/" 我們就可以對路徑進行拼接了,怎麼樣是不是很友善呢。
讀檔案和寫檔案
在我們使用open來操作檔案讀寫操作的時候,不僅可以使用字元串格式的路徑,對于pathlib生成的路徑完全可以直接使用:
path = pathlib.Path.cwd() / 'test.md'
with open(path, mode='r') as fid:
headers = [line.strip() for line in fid if line.startswith('#')]print('\n'.join(headers))
或者在pathlib的基礎使用open,我們推薦使用下面的方式
import pathlib
DIR_PATH = pathlib.Path("/Users/chennan/CDM") / "2000" / "hehe.txt"
with DIR_PATH.open("r") as fs:data = fs.read()
print(data)
這樣寫的好處就是open裡面我們不需要再去傳入路徑了,直接指定檔案讀寫模式即可。實際上這裡的open方法,底層也是調用了os.open的方法。使用哪種方式看個人的喜好。
pathlib還提供幾種檔案的讀寫方式:
可以不用再使用with open的形式即可以進行讀寫。
.read_text(): 找到對應的路徑然後打開檔案,讀成str格式。等同open操作檔案的"r"格式。
.read_bytes(): 讀取位元組流的方式。等同open操作檔案的"rb"格式。
.write_text(): 檔案的寫的操作,等同open操作檔案的"w"格式。
.write_bytes(): 檔案的寫的操作,等同open操作檔案的"wb"格式。
使用resolve可以通過傳入檔案名,來傳回檔案的完整路徑,使用方式如下
import pathlib
py_path =pathlib.Path("superdemo.py")print(py_path.resolve())
輸出
/Users/chennan/pythonproject/demo/superdemo.py
需要注意的是"superdemo.py"檔案要和我目前的程式檔案在同一級目錄。
選擇路徑的不同組成部分。
pathlib還提供了很多路徑操作的屬性,這些屬性可以選擇路徑的不用部位,如
.name: 可以擷取檔案的名字,包含拓展名。.parent: 傳回上級檔案夾的名字
.stem: 擷取檔案名不包含拓展名.suffix: 擷取檔案的拓展名.anchor: 類似盤符的一個東西,
import pathlib
now_path = pathlib.Path.cwd() / "demo.txt"print("name",now_path.name)print("stem",now_path.stem)print("suffix",now_path.suffix)print("parent",now_path.parent)print("anchor",now_path.anchor)
輸出内容如下
name demo.txt
stem demo
suffix .txt
parent /Users/chennan/pythonproject/demo
anchor /
移動和删除檔案
當然pathlib還可以支援檔案其他操作,像移動,更新,甚至删除檔案,但是使用這些方法的時候要小心因為,使用過程不用有任何的錯誤提示即使檔案不存在也不會出現等待的情況。
使用replace方法可以移動檔案,如果檔案存在則會覆寫。為避免檔案可能被覆寫,最簡單的方法是在替換之前測試目标是否存在。
import pathlib
destination = pathlib.Path.cwd() / "target"
source = pathlib.Path.cwd() / "demo.txt"
if not destination.exists():
source.replace(destination)
但是上面的方法存在問題就是,在多個程序多destination進行的操作的時候就會現問題,可以使用下面的方法避免這個問題。也就是說上面的方法适合單個檔案的操作。
import pathlib
destination = pathlib.Path.cwd() / "target"
source = pathlib.Path.cwd() / "demo.txt"
with destination.open(mode='xb') as fid: #xb表示檔案不存在才操作
fid.write(source.read_bytes())
當destination檔案存在的時候上面的代碼就會出現FileExistsError異常。
從技術上講,這會複制一個檔案。 要執行移動,隻需在複制完成後删除源即可。
使用with_name和with.shuffix可以修改檔案名字或者字尾。
import pathlib
source = pathlib.Path.cwd() / "demo.py"
source.replace(source.with_suffix(".txt")) #修改字尾并移動檔案,即重命名
可以使用.rmdir()和.unlink()來删除檔案。
import pathlib
destination = pathlib.Path.cwd() / "target"
source = pathlib.Path.cwd() / "demo.txt"
source.unlink()
幾個pathlib的使用例子
統計檔案個數
我們可以使用.iterdir方法擷取目前檔案下的是以檔案.
import pathlibfrom collections import Counter
now_path = pathlib.Path.cwd()
gen = (i.suffix for i in now_path.iterdir())print(Counter(gen))
輸出内容
Counter({'.py': 16, '': 11, '.txt': 1, '.png': 1, '.csv': 1})
通過配合使用collections子產品的Counter方法,我們擷取了當檔案夾下檔案類型情況。
前面我們說過glob子產品點這裡了解【glob子產品的使用】,同樣的pathlib也有glob方法和rglob方法,不同的是glob子產品裡的glob方法結果是清單形式的,iglob是生成器類型,在這裡pathlib的glob子產品傳回的是生成器類型,然後pathlib還有一個支援遞歸操作的rglob方法。
下面的這個操作我通過使用glob方法,設定規則進行檔案的比對。
import pathlibfrom collections import Counter
gen =(p.suffix for p in pathlib.Path.cwd().glob('*.py'))print(Counter(gen))
展示目錄樹
下一個示例定義了一個函數tree(),該函數的作用是列印一個表示檔案層次結構的可視樹,該樹以一個給定目錄為根。因為想列出其子目錄,是以我們要使用.rglob()方法:
import pathlibfrom collections import Counterdef tree(directory):
print(f'+ {directory}')for path in sorted(directory.rglob('*')):
depth = len(path.relative_to(directory).parts)
spacer = ' ' * depth
print(f'{spacer}+ {path.name}')
now_path = pathlib.Path.cwd()if __name__ == '__main__':
tree(now_path)
其中relative_to的方法的作用是傳回path相對于directory的路徑。
parts方法可以傳回路徑的各部分。例如
import pathlib
now_path = pathlib.Path.cwd()if __name__ == '__main__':print(now_path.parts)
傳回
('/', 'Users', 'chennan', 'pythonproject', 'demo')
擷取檔案最後一次修改時間
iterdir(),.glob()和.rglob()方法非常适合于生成器表達式和清單了解。
使用stat()方法可以擷取檔案的一些基本資訊,使用.stat().st_mtime可以擷取檔案最後一次修改的資訊。
import pathlib
now_path = pathlib.Path.cwd()from datetime import datetime
time, file_path = max((f.stat().st_mtime, f) for f in now_path.iterdir())print(datetime.fromtimestamp(time), file_path)
甚至可以使用類似的表達式擷取上次修改的檔案内容
import pathlibfrom datetime import datetime
now_path =pathlib.Path.cwd()
result = max((f.stat().st_mtime, f) for f in now_path.iterdir())[1]print(result.read_text())
.stat().st_mtime會傳回檔案的時間戳,可以使用datetime或者time子產品對時間格式進行進一步轉換。
其他内容
關于pathlib.Path格式路徑轉換為字元串類型。
因為通過pathlib子產品操作生成的路徑,不能直接應用字元串的一些操作,是以需要轉換成字元串,雖然可以使用str()函數進行轉換,但是安全性不高,建議使用os.fspath()方法,因為如果路徑格式非法的,可以抛出一個異常。str()就不能做到這一點。
拼接符号"/"背後的秘密
/運算符由__truediv__()方法定義。 實際上,如果你看一下pathlib的源代碼,你會看到類似的東西。
class PurePath(object):def __truediv__(self, key):return self._make_child((key,))
後記
從Python 3.4開始,pathlib已在标準庫中提供。 使用pathlib,檔案路徑可以由适當的Path對象表示,而不是像以前一樣用純字元串表示。 這些對象使代碼處理檔案路徑:
- 更容易閱讀,特别是可以使用“/”将路徑連接配接在一起
- 更強大,直接在對象上提供最必要的方法和屬性
- 在作業系統中更加一緻,因為Path對象隐藏了不同系統的特性
在本教程中,你已經了解了如何建立Path對象、讀取和寫入檔案、操作路徑和底層檔案系統,以及如何周遊多個檔案路徑等一系列執行個體。
最後,建議下去自己多加練習,我對文章中的代碼都進行了驗證,不會出現運作錯誤的情況。
原文
https://realpython.com/python-pathlib/
(*本文僅代表作者觀點,轉載請聯系原作者。原文标題:華麗的蛻變-使用Pathlib子產品,檔案操作So Easy!)
◆
精彩推薦
◆