pathlib

https://www.jianshu.com/p/c286ef33e5e1

在 python3.4 之前,你是否這樣判斷一個完全路徑是否是文件

os.path.isfile(os.path.join(os.path.expanduser('~'), 'shark.txt'))

在本教程中,您將瞭解如何使用文件路徑 - 目錄和文件的名稱 - 在Python中。您將學習讀取和寫入文件,操作路徑和底層文件系統的新方法,以及查看如何列出文件和迭代它們的一些示例。使用該pathlib模塊,可以使用優雅,可讀和Pythonic代碼重寫上述示例,如:

import pathlib
(pathlib.Path.home() / 'realpython.txt').is_file()

文件的路徑成爲了一個對象。

Python文件路徑處理的問題

由於許多不同的原因,處理文件和與文件系統交互很重要。最簡單的情況可能只涉及讀取或寫入文件,但有時候會有更復雜的任務。也許您需要列出給定類型的目錄中的所有文件,查找給定文件的父目錄,或創建一個尚不存在的唯一文件名。

傳統上,Python使用常規文本字符串表示文件路徑。在os.path標準庫的支持下,這已經足夠了,雖然有點麻煩(如前面提到的例子)。然而,由於路徑不是字符串,重要的功能是遍佈在標準庫中的,包括像osglobshutil等庫。以下示例僅需要三個import語句來將所有文本文件移動到存檔目錄:

import glob
import os
import shutil

for file_name in glob.glob('*.txt'):
    new_path = os.path.join('archive', file_name)
    shutil.move(file_name, new_path)

對於由字符串表示的路徑,使用常規字符串方法是可能的,但通常是個壞主意。例如,您應該使用os.path.join(),它使用操作系統上正確的路徑分隔符連接路徑,而不是像常規字符串那樣連接兩個路徑。回想一下,Windows使用\,而Mac和Linux使用/作爲分隔符。這種差異可能導致難以發現的錯誤,例如我們在介紹中僅用於Windows路徑的第一個示例。

pathlib模塊是在Python 3.4(PEP 428)中引入的,用於解決這些挑戰。它在一個地方收集必要的功能,並通過易於使用的Path對象上的方法和屬性使其可用。

早期,其他軟件包仍然使用字符串作爲文件路徑,但是從Python 3.6開始,整個標準庫都支持pathlib模塊,部分原因是添加了文件系統路徑協議。

讓我們看看pathlib在實踐中的運作方式。

創建路徑

你真正需要知道的是pathlib.Path類。 創建路徑有幾種不同的方法。 首先,有 .cwd()(當前工作目錄)和 .home()(您的用戶的主目錄)等類方法:

In [10]: import pathlib

In [11]: pathlib.Path.cwd()
Out[11]: PosixPath('/Users/yanshunjun')

注意:在本教程中,我們將假設已導入pathlib,而不會如上所述顯示的讓你看到導入的 pathlib的語句 。 由於您將主要使用Path類,您也可以 from pathlib import Path, 之後使用 Path 調用他的其他方法。

也可以從其字符串表示中顯式創建路徑:

>>> pathlib.Path(r'C:\Users\yanshunjun\sharkyun\file.txt')
WindowsPath('C:/Users/yanshunjun/sharkyun/file.txt')
# 這是一個 Windows 風格的路徑,可以在 Windows 平臺上看到這個效果

處理Windows路徑的一個小提示:在Windows上,路徑分隔符是反斜槓,\。 但是,在許多上下文中,反斜槓也用作轉義字符,以表示不可打印的字符。 爲避免出現問題,請使用原始字符串文字來表示Windows路徑。 這些是字符串文字,其前面有一個r。 在原始字符串文字中,\ 表示字面反斜槓:r'C:\Users'

構造路徑的第三種方法是使用特殊運算符/連接路徑的各個部分。 正斜槓運算符獨立於平臺上的實際路徑分隔符使用:

>>> pathlib.Path.home() / 'python' / 'scripts' / 'test.py'
PosixPath('/Users/yanshunjun/python/scripts/test.py')

只要至少有一個Path對象,/就可以連接多個路徑或路徑和字符串的混合(如上所述)。 如果您不喜歡特殊/符號,則可以使用.joinpath()方法執行相同的操作:

>>> pathlib.Path.home().joinpath('python', 'scripts', 'test.py')
PosixPath('/Users/yanshunjun/python/scripts/test.py')

請注意,在前面的示例中,pathlib.Path由WindowsPath或PosixPath表示。 表示路徑的實際對象取決於底層操作系統。 (即,WindowsPath示例在Windows上運行,而PosixPath示例已在Mac或Linux上運行。)有關詳細信息,請參閱操作系統差異部分。有關詳細信息,請參閱操作系統差異部分。

讀寫文件

傳統上,在Python中讀取或寫入文件的方法是使用內置open()函數。這仍然是正確的,因爲該open()函數可以直接使用Path對象。以下示例查找Markdown文件中的所有的標題,並打印它們:

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))

一個等價的替代方法是在Path對象上調用 .open()

path = pathlib.Path.cwd() / 'test.md'
with path.open(mode='r') as fid:
    ...

實際上,Path.open() 在後臺調用內置的 open() 。 您使用哪個選項主要是品味問題。

對於簡單的文件讀取和寫入,pathlib 庫中有幾種便捷方法:

.read_text():在文本模式下打開路徑並將內容作爲字符串返回。
.read_bytes():以二進制/字節模式打開路徑並將內容作爲字節串返回。
.write_text():打開路徑並向其寫入字符串數據。
.write_bytes():以二進制/字節模式打開路徑並向其寫入數據。
這些方法中的每一個都處理文件的打開和關閉,使得它們使用起來很簡單,例如:

>>> path = pathlib.Path.cwd() / 'test.md'
>>> path.read_text()
<the contents of the test.md-file>

路徑也可以指定爲簡單文件名,在這種情況下,它們是相對於當前工作目錄進行解釋的。以下示例與前一個示例等效:

>>> pathlib.Path('test.md').read_text()
<the contents of the test.md-file>

.resolve()方法將找會到完整路徑。
下面,我們確認當前工作目錄用於簡單文件名:

>>> path = pathlib.Path('test.md')
>>> path.resolve()
PosixPath('/Users/yanshunjun/test.md')
>>> path.resolve().parent == pathlib.Path.cwd()
True

請注意,比較路徑時,將比較它們的表示形式。 在上面的示例中,path.parent不等於pathlib.Path.cwd(),因爲path.parent由 '.' 表示。 而pathlib.Path.cwd()由'/Users/yanshunjun/test.md'表示。

挑選路徑的組成部分

路徑的不同部分可方便地作爲屬性使用。 基本的例子包括:

.name:沒有任何目錄的文件名
.parent:包含該文件的目錄,如果path是目錄,則爲父目錄
.stem:沒有後綴的文件名
.suffix:文件擴展名
.anchor:目錄前路徑的一部分
以下是這些屬性的實際應用:

>>> path = pathlib.Path('/Users/yanshunjun/test.md')
>>> path
PosixPath('/Users/yanshunjun/test.md')
>>> path.name
'test.md'
>>> path.stem
'test'
>>> path.suffix
'.md'
>>> path.parent
PosixPath('/Users/yanshunjun')
>>> path.parent.parent
PosixPath('/Users')
>>> path.anchor
'/'

請注意,.parent 返回一個新Path對象,而其他屬性返回字符串。這意味着,例如,.parent 可以像上一個示例中那樣鏈接,或者甚至與/ 創建全新路徑相結合:

>>> path.parent.parent / ('new' + path.suffix)
PosixPath('/Users/new.md')

優秀的Pathlib Cheatsheet提供了這些以及其他屬性和方法的直觀表示。

移動和刪除文件

通過pathlib,您還可以訪問基本的文件系統級操作,如移動,更新甚至刪除文件。 在大多數情況下,這些方法在信息或文件丟失之前不會發出警告或等待確認。 使用這些方法時要小心。

要移動文件,請使用.replace()。 請注意,如果目標已存在,.replace() 將覆蓋它。 不幸的是,pathlib 沒有明確支持安全移動文件。 爲避免可能覆蓋目標路徑,最簡單的方法是在替換之前測試目標是否存在::

if not destination.exists():
    source.replace(destination)

然而,這確實爲可能的競爭條件敞開了大門。 另一個進程可能會在執行if 語句和 .replace() 方法之間的目標路徑上添加文件。 如果這是一個問題,更安全的方法是打開獨佔創建的目標路徑並顯式複製源數據:

with destination.open(mode='xb') as fid:
    fid.write(source.read_bytes())

如果目標已存在,上面的代碼將引發FileExistsError。 從技術上講,這會複製一個文件。 要執行移動,只需在複製完成後刪除源(請參閱下文)。 確保沒有引起異常。

重命名文件時,有用的方法可能是 .with_name().with_suffix()。 它們都返回原始路徑,但分別替換名稱或後綴。

例如:

>>> path
PosixPath('/home/gahjelle/realpython/test001.txt')
>>> path.with_suffix('.py')
PosixPath('/home/gahjelle/realpython/test001.py')
>>> path.replace(path.with_suffix('.py'))

可以分別使用 .rmdir().unlink() 刪除目錄和文件。(再次提示,小心!)

實用的例子

在本節中,您將看到一些如何使用 pathlib 來處理簡單挑戰的示例。

計算文件
列出許多文件有幾種不同的方法。 最簡單的是 .iterdir() 方法,它迭代給定目錄中的所有文件。 以下示例將 .iterdir()collections.Counter類組合在一起,以計算當前目錄中每種文件類型的文件數:

>>> import collections
>>> collections.Counter(p.suffix for p in pathlib.Path.cwd().iterdir())
Counter({'.md': 2, '.txt': 4, '.pdf': 2, '.py': 1})

可以使用方法.glob().rglob()(遞歸glob)創建更靈活的文件列表。例如,pathlib.Path.cwd().glob('*.txt') 返回.txt當前目錄中帶有後綴的所有文件。以下僅計算從以下p 開始的文件類型。

>>> import collections
>>> collections.Counter(p.suffix for p in pathlib.Path.cwd().glob('*.p*'))
Counter({'.pdf': 2, '.py': 1})

顯示目錄樹

下一個示例定義了一個函數,該tree() 函數將打印一個表示文件層次結構的可視樹,該樹以一個給定目錄爲根。在這裏,我們也要列出子目錄,因此我們使用以下 .rglob() 方法:

def 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}')

請注意,我們需要知道文件所在根目錄的距離。 爲此,我們首先使用 .relative_to() 來表示相對於根目錄的路徑。 然後,我們計算表示中的目錄數(使用.parts屬性)。 運行時,此函數會創建一個如下所示的可視樹:

>>> tree(pathlib.Path.cwd())
+ /home/gahjelle/realpython
    + directory_1
        + file_a.md
    + directory_2
        + file_a.md
        + file_b.pdf
        + file_c.py
    + file_1.txt
    + file_2.txt

注:F-串僅在Python 3.6及更高版本。在舊版本中,f'{spacer}+ {path.name}'表達式可以改寫爲'{0}+ {1}'.format(spacer, path.name)

查找上次修改的文件
這些 .iterdir().glob().rglob()方法非常適合生成器表達式和列表推導。要在上次修改的目錄中查找文件,可以使用該.stat()方法獲取有關基礎文件的信息。例如,.stat().st_mtime 給出上次修改文件的時間:

>>> from datetime import datetime
>>> time, file_path = max((f.stat().st_mtime, f) for f in directory.iterdir())
>>> print(datetime.fromtimestamp(time), file_path)
2018-03-23 19:23:56.977817 /home/gahjelle/realpython/test001.txt

您甚至可以使用類似的表達式獲取上次修改的文件的內容:

>>> max((f.stat().st_mtime, f) for f in directory.iterdir())[1].read_text()
<the contents of the last modified file in directory>

從不同.stat().st_屬性 返回的時間戳表示自1970年1月1日以來的秒數。除了datetime.fromtimestamp之外,還可以使用time.localtimetime.ctime將時間戳轉換爲更有用的值。

創建唯一文件名

最後一個示例將說明如何基於模板構造唯一編號的文件名。 首先,指定文件名的模式,以及計數器的空間。 然後,檢查通過加入目錄和文件名(使用計數器的值)創建的文件路徑的存在。 如果已存在,請增加計數器並再試一次:

def unique_path(directory, name_pattern):
    counter = 0
    while True:
        counter += 1
        path = directory / name_pattern.format(counter)
        if not path.exists():
            return path

path = unique_path(pathlib.Path.cwd(), 'test{:03d}.txt')

如果目錄已包含文件test001.txt和test002.txt,則上面的代碼將設置test003.txt的路徑。

操作系統差異

之前,我們注意到當我們實例化時pathlib.Path,返回了一個WindowsPath或一個PosixPath對象。對象的類型取決於您使用的操作系統。此功能使編寫跨平臺兼容代碼變得相當容易。可以直接明確的使用 WindowsPathPosixPath,但您只會將代碼限制在該系統而且沒有任何好處。像這樣的具體路徑不能在不同的系統上使用:

>>> pathlib.WindowsPath('test.md')
NotImplementedError: cannot instantiate 'WindowsPath' on your system

有時您可能需要表示路徑而無法訪問底層文件系統(在這種情況下,在非Windows系統上表示Windows路徑也可能有意義,反之亦然)。這可以通過PurePath對象完成。但不支持訪問文件系統的方法:

>>> path = pathlib.PureWindowsPath(r'C:\Users\gahjelle\realpython\file.txt')
>>> path.name
'file.txt'
>>> path.parent
PureWindowsPath('C:/Users/gahjelle/realpython')
>>> path.exists()
AttributeError: 'PureWindowsPath' object has no attribute 'exists'

結論
自Python 3.4以來,pathlib已經可以在標準庫中使用。使用pathlib,文件路徑可以由適當的Path對象表示,而不是像以前一樣由純字符串表示。這些對象使代碼處理文件路徑:

更容易閱讀,特別是因爲/用於將路徑連接在一起
更強大,直接在對象上提供大多數必要的方法和屬性
在操作系統中更加一致,因爲不同系統的特性被Path對象隱藏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章