用戶:Xyy23330121/Python/文件系統
模塊 os 提供了許多有效的作業系統工具。本頁省略掉其中一些操作,轉而注重於作業系統中、對文件系統的操作。比如刪除、複製、重命名(移動)、新建等。
除此之外,Python 還額外提供了模塊 shutil 用於進行一些高階文件操作。在一項操作可以用 shutil 中的函數完成時,用 shutil 提供的函數一般更快。
形如 C:\Program Files\Python 的,被稱為是文件路徑。在 Python 中,文件路徑通常是由字符串存儲的。比如:
path_str = r"C:\Program Files\Python" #使用“原始字符串”
Python 也支持使用抽象基類 os.PathLike 的子類來存儲路徑。
當一個類具有 __fspath__ 方法時,它就被視為是 os.PathLike 的子類。
__fspath__ 方法應該返回一個表示路徑的字符串,比如:
class Path:
...
def __fspath__(self):
return r"C:\Program Files\Python"
傳入路徑參數時,可以傳入路徑字符串,也可以傳入 os.PathLike 的子類實例。
本函數輸入一個路徑參數,然後返回字符串形式的路徑。我們看以下示例:
class Path:
def __fspath__(self): return r"C:\Program Files\Python"
p = Path()
print(os.fspath(p)) #输出:C:\Program Files\Python
形如下面的,被稱為是絕對路徑。
path = r"C:\Program Files\Python"
絕對路徑比較好理解,這裏主要描述相對路徑。
命令行程序,比如 Windows 的 cmd、PowerShell,以及 linux 的 terminal,都是有「當前工作目錄」的。這是早期計算機系統,沒有圖形文件管理器時,用命令行程序來管理文件所需的功能。
以 PowerShell 為例,如果運行:
PS D:\test> & python.exe C:\test.py
並從 test.py 中輸出「當前工作路徑」。我們會發現當前工作路徑為:D:\test,即調用 Python 時,PowerShell 的工作路徑。而非 C:\ ,即 test.py 所在的路徑。使用相對路徑時要注意這一點。
相對路徑是相對於當前工作路徑而言的。我們同樣以剛才的例子,如果在 test.py 中,用:
path = "Python"
來表示路徑,則在上面的運行中,path 代表的是 D:\test\Python。
os.getcwd() 會輸出當前工作路徑的字符串。
os.chdir(path) 會把工作路徑設為 path 所示的路徑。
path 可以為表示路徑的字符串,也可以為上面所述的、os.PathLike 的子類。
特別的,. 表示當前路徑,而 .. 表示上一級文件夾。
比如以下的情況:
folder/
test.py
MyModule/
__init__.py
SubModule/
__init__.py為了說明簡單起見,我們把工作路徑設到 SubModule 文件夾中,然後看以下代碼的輸出:
print(os.listdir('..')) #输出:['SubModule', '__init__.py'],说明该路径代表 MyModule 文件夹。
print(os.listdir('.')) #输出:['__init__.py'],说明该路径代表 SubModule 文件夹。
print(os.listdir(r'..\..')) #输出:['MyModule', 'test.py'],说明该路径代表 folder 文件夹。
print(os.listdir(r'..\.\SubModule\..\..')) #输出:['MyModule', 'test.py'],说明该路径代表 folder 文件夹。
其中使用的函數,在本章後面會講到。
在操作路徑時,有時會出現不同系統路徑分隔符不同的情況。比如,我們在路徑 folder 下創建了文件夾 sub,然後想操作 sub 中的內容。使用 Windows 系統的程式設計師,很容易把路徑寫為:
folder = ...
sub = ...
path = folder "\\" sub
但是,這在使用"/"為路徑分隔符的系統中,是不適用的。此時,可以用模塊 os 和 os.path 中提供的內容來解決這個問題。
使用以下方法可以避免出現上面的問題:
import os
folder = ...
sub = ...
path = folder os.sep sub
os.sep會給出當前系統的路徑分隔符。其它的、文件系統所使用的字符串,見下面的表格。
此類型的常量,許多在 os 模塊中和 os.path 模塊中都會有。
| 模塊 | 系統 | 說明 | ||
|---|---|---|---|---|
| os 模塊 | os.path 模塊 | POSIX | Windows | |
| os.curdir | os.path.curdir | '.'
|
系統用於表示當前文件夾的常量字符串。 | |
| os.pardir | os.path.pardir | '..'
|
系統用於表示父文件夾的常量字符串。 | |
| os.sep | os.path.sep | '/' |
'\\'
|
系統路徑分隔符。 |
| os.altsep | os.path.altsep | None |
'/'
|
系統路徑分隔符的替代字符。 |
| os.extsep | os.path.extsep | '.'
|
系統用於分割文件名與拓展名的字符。 | |
| os.pathsep | os.path.pathsep | ':' |
';'
|
系統用於分隔搜索路徑(如 PATH)中不同部分的字符 |
| os.devnull | os.path.devnull | '/dev/null' |
'nul'
|
空設備的文件路徑 |
也可以用這些模塊中,操作路徑的函數來解決問題:
folder = ...
sub = ...
import os.path
path = os.path.join(folder, sub)
path 為一個路徑。如果 path 所示的內容存在,則返回 True。特別的,對於失效的符號連結,返回 False。
除非特殊說明,以下函數的 path 參數均應傳入路徑。
類似 exists,但對於失效的符號連結,返回 True。
如果 path 所示的內容存在,且是一個文件,則返回 True。本方法會跟蹤符號連結,因此,如果 path 所示的符號連結指向一個文件,也會返回 True。
類似 os.path.isfile,但檢查的是文件夾。
如果 path 所示的內容存在,且是符號連結,則返回 True。
如果 path 所示的內容存在,且是 junction,則返回 True。junction 是 NTFS 上獨有的一個類似符號連結的內容。
返回 path 中,所有子文件夾和文件的名稱組成的列表。
類似 os.listdir(),但返回的並非是列表,而是一個特殊的迭代器。該迭代器中的元素為 os.DirEntry 類型。這種類型的元素包含了文件類型或文件屬性信息,因此,如果代碼需要查詢文件類型或文件屬性,用 scandir 而非 listdir 會提升性能。
迭代器的迭代順序可能是任意順序,而非瀏覽文件夾時常有的「字母順序」或「更改時間順序」等。
對於由 os.scandir 生成的迭代器 scandir,該序列包含一個方法 close。
scandir.close() 會關閉該迭代器,並釋放佔用的資源。
儘管該方法在迭代結束或出錯時會自動調用。但最好還是在迭代結束後手動調用一遍該方法,或者使用 with 語句。
由 os.scandir 生成的、迭代器中的迭代元素。該元素有以下屬性和方法:
文件或文件夾的名稱。
文件或文件夾的路徑。如果 os.scandir 的參數為絕對路徑,則這裏為絕對路徑;如果 os.scandir 的參數為相對路徑或空缺,則這裏為相對路徑。
相對路徑是相對於使用 os.scandir 時的工作路徑的。
如果是文件夾,或是指向文件夾的符號連結,則返回 True。反之,返回 False。
若 follow_symlinks 為 False,則該迭代元素指向符號連結時,也會返回 False。
類似is_dir(),如果是文件,則返回 True。
類似is_dir(),但返回詳細的屬性。屬性將以 stat_result 類型的形式返回。
關於 stat_result 的詳細信息,參見下面的章節。
返回迭代元素所對應內容的索引節點號。
如果是符號連結,返回 True。反之,返回 False。
如果是junction,則返回 True。反之,返回 False。
os.walk(top, topdown=True, onerror=None, followlinks=False)
返回一個可迭代對象。對 top 中的每個文件夾,都會在可迭代對象中增加元素 (dirpath, dirnames, filenames)。
其中,dirpath 是文件夾路徑,dirnames 是文件夾中,子文件夾的名稱列表,filenames 是文件夾中的文件名稱列表。比如以下的情況:
folder/
test.py
MyModule/
__init__.py
SubModule/
__init__.py如果把工作路徑設到 folder 所在的路徑,再用 os.walk('folder'),則會有形如以下的元素被添加到可迭代對象中:
('folder', ['MyModule'], ['test.py'])
('folder\\MyModule', ['SubModule'], ['__init__.py'])
('folder\\MyModule\\SubModule', [], ['__init__.py'])若 topdown 為 True,則增加元素的方式是遞歸地。在迭代時,它會先生成包含 top 的三元組。下一次迭代時,存儲第一次迭代時得到的三元組,再按其中 dirnames 里的子文件夾進行迭代,生成來自這些子文件夾的三元組。以此類推。
特別的,此時,在迭代過程中,可以直接對三元組中的 dirname ,使用列表的方法來進行修改,以控制之後的迭代順序與內容。
若 topdown 為 False。則會先生成父文件夾中、所有子文件夾的三元組,再生成父文件夾。此時,更改 dirname 的內容對迭代沒有影響。可以用此方法來從後往前刪除空文件夾。
onerror 為一個錯誤處理函數。該函數輸入一個 OSError 實例。在不輸入此函數時,os.walk 默認會忽略它調用 os.scandir 時產生的錯誤。若輸入了此函數,讀者可以通過自定義該函數,決定出錯時是報錯並停止、只輸出錯誤信息還是忽略錯誤。
輸入 top 的內容與函數的結果有關。我們依舊是上面的例子,運行:
for obj in os.walk(r"folder\."):
print(obj)
輸出為:
('folder\\.', ['MyModule'], ['test.py'])
('folder\\.\\MyModule', ['SubModule'], ['__init__.py'])
('folder\\.\\MyModule\\SubModule', [], ['__init__.py'])特別的,向 top 傳入 "."、".." 等來選擇工作路徑,或工作路徑的上級路徑,也是有效的。如果工作路徑在運行過程中可能會改變,最好還是對 top 使用絕對路徑。
os.mkdir(path, mode=0o777, *, dir_fd=None)
本函數會創建路徑為 path 的文件夾。但只創建「一層」:比如向 path 傳入 C:\folder\folder,而 C:\folder 不存在時,會報錯 FileNotFoundError。
如果 path 已經存在,則會報錯 FileExistsError。
mode 參數對各個不同系統有區別,為了代碼的可移植性,最好不要進行自定義。dir_fd 是指向目錄 dirpath 的文件描述符,在一些平台上不被支持,因此也不多贅述。如果讀者需要了解關於 dir_fd 的內容,參見 python文檔 。
本函數會遞歸地創建路徑為 name 的文件夾。比如向 name 傳入 C:\folder\folder,而 C:\folder 不存在時,它會調用 os.mkdirs("C:\folder", mode)。
若 exist_ok 為 False,當 name 所示路徑已經存在時,會報錯 FileExistsError。
mode 參數對各個不同系統有區別。
本函數會刪除空文件夾。僅刪除一層:比如向 path 傳入 C:\folder\folder 時,僅刪除 C:\folder\folder,而保留 C:\folder。
如果目錄不存在,則報錯 FileNotFoundError。如果目錄不為空,則報錯 OSError。
本函數會刪除空文件夾。但不止刪除一層:比如向 name 傳入 C:\folder\folder 時,會先刪除 C:\folder\folder,再檢查 C:\folder 是否是空文件夾,如果是,則繼續進行刪除。直到某一層不是空文件夾為止。
如果無法成功刪除 name 所示的文件夾,則會報錯 OSError。
本函數僅用於刪除文件。
如果 path 是目錄,則會報錯 OSError。如果 path 不存在,則會報錯 FileNotFoundError。
還有一個函數 os.unlink 和本函數完全一致。unlink 是其傳統的 Unix 名稱。
shutil.rmtree(path, ignore_errors=False, *, onexc=None, dir_fd=None)
此函數會刪除 path 所示文件夾以及其中的所有內容。path 必須是文件夾。
如果 ignore_errors 為 True,則在刪除失敗時,不會進行報錯。
onexc 參數是用於處理異常的函數。只有 ignore_errors 為 False 時,才會嘗試讓 onexc 來處理異常。
如果傳入了 onexc 參數,並且在刪除時引發了異常,則會將異常信息發送給該參數,並由該參數進行處理。它會按順序傳入三個信息:
function: 刪除過程中,引發異常的函數。
它依賴於運行的平台和實現。path:刪除過程中,function被傳入的路徑。excinfo:出錯時,異常的實例。
如果在運行 onexc 函數時出錯,則不會再把異常捕獲並發送給 onexc,而是直接進行處理。
Python 的文檔提供了以下示例:
刪除只讀文件也會導致報錯。通過以下的方式,可以刪除只讀文件。
import os, stat
import shutil
def remove_readonly(func, path, _):
"Clear the readonly bit and reattempt the removal"
os.chmod(path, stat.S_IWRITE)
func(path)
shutil.rmtree(directory, onexc=remove_readonly)
src 和 dst 都是路徑。本函數會將 src 中的內容複製到 dst。比如以下示例:
import os, stat
import shutil
shutil.copyfile("测试.txt", "测试(副本).txt")
該實例運行的結果是,新建「測試(副本).txt」,再把「測試.txt」中的全部內容複製到「測試(副本).txt」中。
如果 dst 是已經存在的文件,則會替換掉該文件。如果 src 和 dst 指向同一個文件,則會引發錯誤 shutil.SameFileError。
follow_symlinks 用於決定 src 是文件連結時的處理方式。如果 follow_symlinks 為 False 且 src 為符號連結,則複製的結果是一個新的符號連結,而不是 src 所指向的文件內容。
在複製完成後,本函數會返回 dst。
和 shutil.copyfile 幾乎一致。只有一點不同:
如果 dst 指向一個已存在的文件夾,則會在 dst 中按 src 所示的文件名新建文件,再複製數據。
類似 shutil.copyfile ,但是會保留文件的元數據(比如修改時間等)。
保留元數據的功能並非所有平台都可以使用,讀者可以按照 該文檔中的描述 檢查各個平台能否保留文件的相應元數據。本函數會儘可能地保留元數據,在無法保留元數據時,也不會報錯。
shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False)
該函數用於直接複製整個文件夾以及其中的內容。它會把文件夾 src 中的所有內容複製到文件夾 dst 中。
具體來講,它會先用類似 os.mkdirs 函數的方式創建 dst 目錄。如果 dst 目錄已存在,則會報錯 FileExistsError 並停止拷貝。
如果 dir_exist_ok 為 True,則即便 dst 目錄已存在,也不會報錯,而是繼續拷貝。
copy_function 決定了本函數會用什麼函數來複製這些文件。默認使用 shutil.copy2 來複製這些文件。
上面所示的 shutil.copyfile、shutil.copy、shutil.copy2 都可以使用。如果要自定義該函數,則該函數必須能傳入三個參數:
src複製文件的來源dst複製到的位置follow_symlinks是否追蹤符號連結
本函數中,如果 symlinks 為 True。則複製函數中的參數 follow_symlinks 取 False。
若 symlinks 為 True,而符號連結指向的文件不存在時,本函數會在拷貝完成後報錯 shutil.Error。shutil.Error 是一個包含該函數中出現的所有異常組成的異常組,它會在複製結束後被引發。
ignore 應傳入一個決定哪些文件 / 文件夾不被複製的函數。
如果 ignore 不為 None,本函數的複製流程經簡化後大致如下:
- 把
src中所有文件和文件夾的名稱添加到「代辦列表」 - 把
src和「代辦列表」傳入ignore函數,得到「忽略列表」 - 遍歷「代辦列表」:
- 如果在「忽略列表」中,則忽略。
- 其餘的,如果是文件夾,則把該文件夾再次傳入
shutil.copytree中。 - 其餘的,用
copy_function來複製文件。
於是,對於被複製的一個文件夾 folder,ignore 函數會被分別傳入:
os.fspath(folder),即folder的字符串形式。os.listdir(folder),即folder路徑中的內容。
它應該返回 os.listdir(folder) 中元素組成的一個列表(或集合等,可以用比較運算符 in 的類型),包含不會被複製的子文件夾名稱和子文件名稱。
可以用這個函數創建 ignore 函數。
該函數返回的對象,會把符合 *patterns 的所有子文件名、子文件夾名都添加到「忽略列表」中。匹配是按照 模塊 fnmatch 的規則進行匹配的。
移動 src 所示的文件或文件夾,使其路徑變為 dst。
注意,dst 只能被創建「一層」,其父文件夾必須存在。如果對以下所示的文件樹:
folder/
a.txt將工作路徑設到 folder,然後執行 os.rename("a.txt",r"a\a.txt") 時,會報錯。因為文件夾 folder\a 是不存在的。
對於比如 dst 已經存在的情況。該函數在不同系統中的具體表現是不同的。Python 文檔列舉了兩個系統的以下情況:
| 狀況 | Windows | Unix | ||
|---|---|---|---|---|
| dst 已經存在 | src 是文件而 dst 是目錄 | 報錯:FileExi
|
報錯 ISADirectoryError
| |
| 兩者都 是目錄 |
dst 是空目錄 | src 中的內容被移動到 dst 中 | ||
| dst 是非空目錄 | 報錯 OSError
| |||
| 兩者都 是文件 |
用戶有權限 | 替換掉 dst 的內容 | ||
| src 和 dst 在不同文件系統上 | 可能會失敗,此時建議使用 shutil.move()
| |||
本函數和 os.rename 的關係,類似 os.mkdir 和 os.mkdirs 的關係。
如果新路徑 new 所需要的父文件夾不存在,則會先把父文件夾創建完畢。
在移動完成後,將調用 removedirs 刪除舊路徑中的空文件夾。
類似 os.rename。但是當 dst 存在且為文件時,會對其進行替換。
先將 src 所示文件或文件夾用 copy_function 複製到 dst,再把 src 刪除。最後,返回 dst。
如果 dst 指向一個已存在的文件夾。那麼,在複製時,會在 dst 中按 src 所示的文件名新建文件並進行複製。
如果 dst 已存在但不是文件夾,則按照 os.rename 的覆寫規則決定是覆寫還是報錯。
如果 src 和 dst 使用相同的文件系統,則不會使用 copy_function,而是改為直接調用 os.rename。
| 屬性名 | 解釋 | |
|---|---|---|
| Unix | Windows | |
| st_mode | 文件模式:文件類型和權限位 | |
| st_ino | 索引節點號 inode number | 文件索引號[3] |
| st_dev | 文件所在設備的設備標識符 | |
| st_nlink | 硬連結的數量 | |
| st_uid | 文件所有者的用戶 ID | |
| st_gid | 文件所有者的用戶組 ID | |
| st_size | 文件大小,以字節(B)為單位的整數 | |
| 時間戳(紀元時間) | ||
| st_atime | 最近訪問時間,以秒為單位 | |
| st_atime_ns | 最近訪問時間,以納秒為單位的整數 | |
| st_mtime | 最近修改時間,以秒為單位 | |
| st_mtime_ns | 最近修改時間,以納秒為單位的整數 | |
| st_ctime | 元數據的最近修改時間 | 不建議使用: 文件創建時間。 |
| st_ctime_ns | 類似 st_ctime,但是以納秒為單位 | |
| st_birthtime | 文件創建時間,以秒為單位 該屬性並不總是可用,使用時可能報錯 AttributeError
| |
| st_birthtime_ns | 類似 st_birthtime,但是以納秒為單位 | |
我們有以下方式查看文件元信息。
對於文件 path,返回一個包含文件元信息的對象 os.stat_result。
os.stat_result 的屬性見右邊。
除右側的屬性外,還可以用 stat 模塊 中提供的一些方式去讀取這個結果。這裏不多贅述。
返回最後訪問時間,為紀元秒數的浮點數。如果該文件不存在或不可訪問,則報錯 OSError。
返回最後修改時間,為紀元秒數的浮點數。如果該文件不存在或不可訪問,則報錯 OSError。
返回元數據最後修改時間,為紀元秒數的浮點數。如果該文件不存在或不可訪問,則報錯 OSError。
在 Windows 上,返回創建時間,為紀元秒數的浮點數。
返回文件大小,以字節為單位的整數。如果該文件不存在或不可訪問,則報錯 OSError。
設置文件的「最近訪問時間」和「最近修改時間」。如果不指定時間,則以當前時間(納秒)來進行設置。
如果傳入 ns,則應傳入一個(访问时间, 修改时间)的元組。其中每個元素是以納秒為單位的整數(紀元時間)。
如果傳入 times,則應傳入一個(访问时间, 修改时间)的元組。其中每個元素是以秒為單位的整數(紀元時間)。
同時傳入 ns 和 times 會出錯。
設置 dst 的權限位,使其和 src 的權限位一致。
設置 dst 的權限位、最近訪問時間、最近修改時間以及旗標,使其和 src 的元數據一致。
對於給出的路徑 path,返回其標準化的絕對路徑。
「標準化」指的是形如 folder\\foo\\.. 的內容會被簡化為等效的 folder。
如果路徑是絕對路徑,返回 True。
在 Unix 上,這代表路徑以斜槓開頭。而在 Windows 上,路徑可以為:
- 驅動器號+冒號開頭,比如
C:和C:\Program Files - 反斜槓開頭,比如
\Program Files。 - 雙反斜槓開頭(對於本地網絡共享的文件使用的UNC路徑),比如
\\host\sharename。
其它類似的判斷,還有判斷掛載點的 os.path.ismount(path)[4] 和判斷 Windows Dev 驅動器的 os.path.isdevdrive(path)[5]。具體請參見相關文檔。
對於給出的路徑 path,返回其標準化的路徑。
消除路徑中的符號連結,返回指向文件的真實路徑。
對路徑的分割操作都是字符串操作。
返迴路徑所對應的文件名 / 文件夾名。我們有以下示例:
import os.path
path = "C:\\Program Files"
print(os.path.basename(path)) #输出:Program Files
返迴路徑中文件名 / 文件夾名以外的部分。該部分的結尾不會有分隔符。我們有以下示例:
import os.path
path = "C:\\Program Files"
print(os.path.dirname(path)) #输出:C:
返回 (os.path.dirname(path), os.path.basename(path))
其它類似的分割,還有 os.path.splitdrive(path)[6]、os.path.splitroot(path)[7] 和 os.path.splitext(path)[8]。這裏不多贅述。
對於包含多個路徑的序列 paths,輸出其公共的最小父文件夾。我們有以下示例:
import os.path
paths = ["C:\\Program Files", "C:\\Program Files (x86)"]
print(os.path.commonpath(paths)) #输出:"C:\\"
對於包含多個路徑的序列 paths,輸出其公共的「前綴」。我們有以下示例:
import os.path
paths = ["C:\\Program Files", "C:\\ProgramData"]
print(os.path.commonpath(paths)) #输出:"C:\\Program"
將參數中開頭部分的 ~ 或 ~user 替換為當前用戶的用戶目錄,並返回替換的結果。
輸入帶有環境變量的路徑,返迴環境變量展開後的路徑。對於 Windows,環境路徑可以參照以下頁面:
本函數會展開 path 中形如 ${name} 的部分。對於 Windows,還會展開形如 %name% 的部分。其中,name 指的是環境變量名稱。
如果系統支持使用任意 unicode 字符(分隔符等除外)作為文件名,則此項為 True。
shutil.make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]]) shutil.get_archive_formats() shutil.register_archive_format(name, function[, extra_args[, description]]) shutil.unregister_archive_format(name) shutil.unpack_archive(filename[, extract_dir[, format[, filter]]]) shutil.register_unpack_format(name, extensions, function[, extra_args[, description]]) shutil.unregister_unpack_format(name) shutil.get_unpack_formats()
shutil.disk_usage(path) shutil.chown(path, user=None, group=None) shutil.which(cmd, mode=os.F_OK | os.X_OK, path=None)
- ↑ 1.0 1.1 path-like object https://docs.python.org/zh-cn/3.12/glossary.html#term-path-like-object
- ↑ os.getcwdb() https://docs.python.org/zh-cn/3.12/library/os.html#os.getcwdb
- ↑ https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information?redirectedfrom=MSDN
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.ismount
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.isdevdrive
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splitdrive
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splitroot
- ↑ https://docs.python.org/zh-cn/3.13/library/os.path.html#os.path.splittext