使用者: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.basename(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