每週一個 Python 模組 | pathlib

yongxinz發表於2019-02-11

專欄地址:每週一個 Python 模組

使用物件導向的 API 而不是低階字串操作來解析,構建,測試和以其他方式處理檔名和路徑。

構建路徑

要建立引用相對於現有路徑值的新路徑,可以使用 / 運算子來擴充套件路徑,運算子的引數可以是字串或其他路徑物件。

import pathlib

usr = pathlib.PurePosixPath('/usr')
print(usr)	# /usr

usr_local = usr / 'local'
print(usr_local)	# /usr/local

usr_share = usr / pathlib.PurePosixPath('share')
print(usr_share)	# /usr/share

root = usr / '..'
print(root)	# /usr/..

etc = root / '/etc/'
print(etc)	# /etc
複製程式碼

正如root示例所示,運算子在給定路徑值時將它們組合在一起,並且在包含父目錄引用時不會對結果進行規範化 ".."。但是,如果某段以路徑分隔符開頭,則會以與之相同的方式將其解釋為新的“根”引用 os.path.join(),從路徑值中間刪除額外路徑分隔符,如此處的etc示例所示。

路徑類包括 resolve() 方法,通過檢視目錄和符號連結的檔案系統以及生成名稱引用的絕對路徑來規範化路徑。

import pathlib

usr_local = pathlib.Path('/usr/local')
share = usr_local / '..' / 'share'
print(share.resolve())	# /usr/share
複製程式碼

這裡相對路徑轉換為絕對路徑 /usr/share。如果輸入路徑包含符號連結,那麼也會擴充套件這些符號連結以允許已解析的路徑直接引用目標。

要在事先不知道段時構建路徑,請使用 joinpath(),將每個路徑段作為單獨的引數傳遞。

import pathlib

root = pathlib.PurePosixPath('/')
subdirs = ['usr', 'local']
usr_local = root.joinpath(*subdirs)
print(usr_local)	# /usr/local
複製程式碼

/運算子一樣,呼叫joinpath()會建立一個新例項。

給定一個現有的路徑物件,很容易構建一個具有微小差異的新物件,例如引用同一目錄中的不同檔案,使用with_name()建立替換檔名的新路徑。使用 with_suffix()建立替換副檔名的新路徑。

import pathlib

ind = pathlib.PurePosixPath('source/pathlib/index.rst')
print(ind)	# source/pathlib/index.rst

py = ind.with_name('pathlib_from_existing.py')
print(py)	# source/pathlib/pathlib_from_existing.py

pyc = py.with_suffix('.pyc')
print(pyc)	# source/pathlib/pathlib_from_existing.pyc
複製程式碼

兩種方法都返回新物件,原始檔案保持不變。

解析路徑

Path 物件具有從名稱中提取部分值的方法和屬性。例如,parts 屬性生成一系列基於路徑分隔符解析的路徑段。

import pathlib

p = pathlib.PurePosixPath('/usr/local')
print(p.parts)	# ('/', 'usr', 'local')
複製程式碼

序列是一個元組,反映了路徑例項的不變性。

有兩種方法可以從給定的路徑物件“向上”導航檔案系統層次結構。parent 屬性引用包含路徑的目錄的新路徑例項,通過 os.path.dirname() 返回值。parents 屬性是一個可迭代的,它產生父目錄引用,不斷地“向上”路徑層次結構,直到到達根目錄。

import pathlib

p = pathlib.PurePosixPath('/usr/local/lib')

print('parent: {}'.format(p.parent))

print('\nhierarchy:')
for up in p.parents:
    print(up)
    
# output
# parent: /usr/local
# 
# hierarchy:
# /usr/local
# /usr
# /
複製程式碼

該示例遍歷parents屬性並列印成員值。

可以通過路徑物件的屬性訪問路徑的其他部分。name 屬性儲存路徑的最後一部分,位於最終路徑分隔符(os.path.basename() 生成的相同值)之後。suffix屬性儲存擴充套件分隔符後面的值,stem屬性儲存字尾之前的名稱部分。

import pathlib

p = pathlib.PurePosixPath('./source/pathlib/pathlib_name.py')
print('path  : {}'.format(p))	# path  : source/pathlib/pathlib_name.py
print('name  : {}'.format(p.name))	# name  : pathlib_name.py
print('suffix: {}'.format(p.suffix))	# suffix: .py
print('stem  : {}'.format(p.stem))	# stem  : pathlib_name
複製程式碼

雖然suffixstem值類似於 os.path.splitext() 生成的值,但值僅基於 name 而不是完整路徑。

建立具體路徑

Path 可以從引用檔案系統上的檔案,目錄或符號連結名稱(或潛在名稱)的字串建立具體類的例項。該類還提供了幾種方便的方法,用於使用常用的更改位置(例如當前工作目錄和使用者的主目錄)構建例項。

import pathlib

home = pathlib.Path.home()
print('home: ', home)	# home:  /Users/dhellmann

cwd = pathlib.Path.cwd()
print('cwd : ', cwd)	# cwd :  /Users/dhellmann/PyMOTW
複製程式碼

目錄內容

有三種方法可以訪問目錄列表,以發現檔案系統上可用檔案的名稱。iterdir() 是一個生成器,Path 為包含目錄中的每個項生成一個新例項。

import pathlib

p = pathlib.Path('.')

for f in p.iterdir():
    print(f)
    
# output
# example_link
# index.rst
# pathlib_chmod.py
# pathlib_convenience.py
# pathlib_from_existing.py
# pathlib_glob.py
# pathlib_iterdir.py
# pathlib_joinpath.py
# pathlib_mkdir.py
# pathlib_name.py
複製程式碼

如果Path不引用目錄,則iterdir() 引發NotADirectoryError

glob() 僅查詢與模式匹配的檔案。

import pathlib

p = pathlib.Path('..')

for f in p.glob('*.rst'):
    print(f)
    
# output
# ../about.rst
# ../algorithm_tools.rst
# ../book.rst
# ../compression.rst
# ../concurrency.rst
# ../cryptographic.rst
# ../data_structures.rst
# ../dates.rst
# ../dev_tools.rst
# ../email.rst
複製程式碼

glob 處理器支援使用模式字首進行遞迴掃描 **或通過呼叫rglob()而不是glob()

import pathlib

p = pathlib.Path('..')

for f in p.rglob('pathlib_*.py'):
    print(f)
    
# output
# ../pathlib/pathlib_chmod.py
# ../pathlib/pathlib_convenience.py
# ../pathlib/pathlib_from_existing.py
# ../pathlib/pathlib_glob.py
# ../pathlib/pathlib_iterdir.py
# ../pathlib/pathlib_joinpath.py
# ../pathlib/pathlib_mkdir.py
# ../pathlib/pathlib_name.py
# ../pathlib/pathlib_operator.py
# ../pathlib/pathlib_ownership.py
# ../pathlib/pathlib_parents.py
複製程式碼

由於此示例從父目錄開始,因此需要進行遞迴搜尋以查詢匹配的示例檔案 pathlib_*.py

讀寫檔案

每個 Path 例項都包含用於處理它所引用的檔案內容的方法。要讀取內容,使用 read_bytes()read_text()。要寫入檔案,使用 write_bytes()write_text()

使用 open() 方法開啟檔案並保留檔案控制程式碼,而不是將名稱傳遞給內建 open() 函式。

import pathlib

f = pathlib.Path('example.txt')

f.write_bytes('This is the content'.encode('utf-8'))

with f.open('r', encoding='utf-8') as handle:
    print('read from open(): {!r}'.format(handle.read()))

print('read_text(): {!r}'.format(f.read_text('utf-8')))

# output
# read from open(): 'This is the content'
# read_text(): 'This is the content'
複製程式碼

操作目錄和符號連結

import pathlib

p = pathlib.Path('example_dir')

print('Creating {}'.format(p))
p.mkdir()

# output
# Creating example_dir
# Traceback (most recent call last):
#   File "pathlib_mkdir.py", line 16, in <module>
#     p.mkdir()
#   File ".../lib/python3.6/pathlib.py", line 1226, in mkdir
#     self._accessor.mkdir(self, mode)
#   File ".../lib/python3.6/pathlib.py", line 387, in wrapped
#     return strfunc(str(pathobj), *args)
# FileExistsError: [Errno 17] File exists: 'example_dir'
複製程式碼

如果路徑已存在,則 mkdir() 引發 FileExistsError

使用 symlink_to() 建立符號連結,連結將根據路徑的值命名,並將引用作為 symlink_to() 引數給出的名稱。

import pathlib

p = pathlib.Path('example_link')

p.symlink_to('index.rst')

print(p)	# example_link
print(p.resolve().name)	# index.rst
複製程式碼

此示例建立一個符號連結,然後用 resolve() 讀取連結來查詢它指向的內容,並列印名稱。

檔案型別

此示例建立了幾種不同型別的檔案,並測試這些檔案以及本地作業系統上可用的一些其他特定於裝置的檔案。

import itertools
import os
import pathlib

root = pathlib.Path('test_files')

# Clean up from previous runs.
if root.exists():
    for f in root.iterdir():
        f.unlink()
else:
    root.mkdir()

# Create test files
(root / 'file').write_text('This is a regular file', encoding='utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))

# Check the file types
to_scan = itertools.chain(
    root.iterdir(),
    [pathlib.Path('/dev/disk0'), pathlib.Path('/dev/console')],
)
hfmt = '{:18s}' + ('  {:>5}' * 6)
print(hfmt.format('Name', 'File', 'Dir', 'Link', 'FIFO', 'Block', 'Character'))
print()

fmt = '{:20s}  ' + ('{!r:>5}  ' * 6)
for f in to_scan:
    print(fmt.format(
        str(f),
        f.is_file(),
        f.is_dir(),
        f.is_symlink(),
        f.is_fifo(),
        f.is_block_device(),
        f.is_char_device(),
    ))
    
# output
# Name                 File    Dir   Link   FIFO  Block  Character
# 
# test_files/fifo       False  False  False   True  False  False
# test_files/file        True  False  False  False  False  False
# test_files/symlink     True  False   True  False  False  False
# /dev/disk0            False  False  False  False   True  False
# /dev/console          False  False  False  False  False   True
複製程式碼

每一種方法,is_dir()is_file()is_symlink()is_socket()is_fifo()is_block_device()is_char_device(),都不帶任何引數。

檔案屬性

可以使用方法 stat()lstat()(用於檢查可能是符號連結的某些內容的狀態)訪問有關檔案的詳細資訊 。這些方法與 os.stat()os.lstat() 產生相同的結果。

# pathlib_stat.py 

import pathlib
import sys
import time

if len(sys.argv) == 1:
    filename = __file__
else:
    filename = sys.argv[1]

p = pathlib.Path(filename)
stat_info = p.stat()

print('{}:'.format(filename))
print('  Size:', stat_info.st_size)
print('  Permissions:', oct(stat_info.st_mode))
print('  Owner:', stat_info.st_uid)
print('  Device:', stat_info.st_dev)
print('  Created      :', time.ctime(stat_info.st_ctime))
print('  Last modified:', time.ctime(stat_info.st_mtime))
print('  Last accessed:', time.ctime(stat_info.st_atime))

# output
# $ python3 pathlib_stat.py
# 
# pathlib_stat.py:
#   Size: 607
#   Permissions: 0o100644
#   Owner: 527
#   Device: 16777220
#   Created      : Thu Dec 29 12:38:23 2016
#   Last modified: Thu Dec 29 12:38:23 2016
#   Last accessed: Sun Mar 18 16:21:41 2018
# 
# $ python3 pathlib_stat.py index.rst
# 
# index.rst:
#   Size: 19569
#   Permissions: 0o100644
#   Owner: 527
#   Device: 16777220
#   Created      : Sun Mar 18 16:11:31 2018
#   Last modified: Sun Mar 18 16:11:31 2018
#   Last accessed: Sun Mar 18 16:21:40 2018
複製程式碼

輸出將根據示例程式碼的安裝方式而有所不同,嘗試在命令列上傳遞不同的檔名 pathlib_stat.py

為了更簡單地訪問有關檔案所有者的資訊,使用 owner()group()

import pathlib

p = pathlib.Path(__file__)

print('{} is owned by {}/{}'.format(p, p.owner(), p.group()))

# output
# pathlib_ownership.py is owned by dhellmann/dhellmann
複製程式碼

touch() 方法類似於 Unix 的 touch 命令,建立檔案或更新現有檔案的修改時間和許可權。

# pathlib_touch.py 

import pathlib
import time

p = pathlib.Path('touched')
if p.exists():
    print('already exists')
else:
    print('creating new')

p.touch()
start = p.stat()

time.sleep(1)

p.touch()
end = p.stat()

print('Start:', time.ctime(start.st_mtime))
print('End  :', time.ctime(end.st_mtime))

# output
# $ python3 pathlib_touch.py
# 
# creating new
# Start: Sun Mar 18 16:21:41 2018
# End  : Sun Mar 18 16:21:42 2018
# 
# $ python3 pathlib_touch.py
# 
# already exists
# Start: Sun Mar 18 16:21:42 2018
# End  : Sun Mar 18 16:21:43 2018
複製程式碼

執行多次此示例會在後續執行中更新現有檔案。

許可權

在類 Unix 系統上,可以使用 chmod() 更改檔案許可權,將模式作為整數傳遞。可以使用stat模組中定義的常量構造模式值。此示例切換使用者的執行許可權位。

import os
import pathlib
import stat

# Create a fresh test file.
f = pathlib.Path('pathlib_chmod_example.txt')
if f.exists():
    f.unlink()
f.write_text('contents')

# Determine what permissions are already set using stat.
existing_permissions = stat.S_IMODE(f.stat().st_mode)
print('Before: {:o}'.format(existing_permissions))	# Before: 644

# Decide which way to toggle them.
if not (existing_permissions & os.X_OK):
    print('Adding execute permission')	# Adding execute permission
    new_permissions = existing_permissions | stat.S_IXUSR
else:
    print('Removing execute permission')
    # use xor to remove the user execute permission
    new_permissions = existing_permissions ^ stat.S_IXUSR

# Make the change and show the new value.
f.chmod(new_permissions)
after_permissions = stat.S_IMODE(f.stat().st_mode)
print('After: {:o}'.format(after_permissions))	# After: 744
複製程式碼

刪除

有兩種方法可以從檔案系統中刪除內容,具體取決於型別。要刪除空目錄,使用 rmdir()

import pathlib

p = pathlib.Path('example_dir')

print('Removing {}'.format(p))
p.rmdir()

# output
# Removing example_dir
# Traceback (most recent call last):
#   File "pathlib_rmdir.py", line 16, in <module>
#     p.rmdir()
#   File ".../lib/python3.6/pathlib.py", line 1270, in rmdir
#     self._accessor.rmdir(self)
#   File ".../lib/python3.6/pathlib.py", line 387, in wrapped
#     return strfunc(str(pathobj), *args)
# FileNotFoundError: [Errno 2] No such file or directory: 'example_dir'
複製程式碼

如果目錄不存在會引發錯誤 FileNotFoundError,嘗試刪除非空目錄也是錯誤的。

對於檔案,符號連結和大多數其他路徑型別使用 unlink()

import pathlib

p = pathlib.Path('touched')

p.touch()

print('exists before removing:', p.exists())	# exists before removing: True

p.unlink()

print('exists after removing:', p.exists())	# exists after removing: False
複製程式碼

使用者必須具有刪除檔案,符號連結,套接字或其他檔案系統物件的許可權。

相關文件:

pymotw.com/3/pathlib/i…

相關文章