by 軒轅御龍
Python os 模塊詳解
1. 簡介
os
就是“operating system”的縮寫,顧名思義,os
模塊提供的就是各種 Python 程序與操作系統進行交互的接口。通過使用os
模塊,一方面可以方便地與操作系統進行交互,另一方面頁可以極大增強代碼的可移植性。如果該模塊中相關功能出錯,會拋出OSError
異常或其子類異常。
注意,如果是讀寫文件的話,建議使用內置函數
open()
;如果是路徑相關的操作,建議使用os
的子模塊os.path
;如果要逐行讀取多個文件,建議使用fileinput
模塊;要創建臨時文件或路徑,建議使用tempfile
模塊;要進行更高級的文件和路徑操作則應當使用shutil
模塊。
當然,使用os
模塊可以寫出操作系統無關的代碼並不意味着os
無法調用一些特定系統的擴展功能,但要切記一點:一旦這樣做就會極大損害代碼的可移植性。
此外,導入os
模塊時還要小心一點,千萬不要為了圖調用省事兒而將os
模塊解包導入,即不要使用from os import *
來導入os
模塊;否則os.open()
將會覆蓋內置函數open()
,從而造成預料之外的錯誤。
2. 常用功能
注意,
os
模塊中大多數接受路徑作為參數的函數也可以接受“文件描述符”作為參數。文件描述符:file descriptor,在 Python 文檔中簡記為 fd,是一個與某個打開的文件對象綁定的整數,可以理解為該文件在系統中的編號。
2.1 os.name
該屬性寬泛地指明了當前 Python 運行所在的環境,實際上是導入的操作系統相關模塊的名稱。這個名稱也決定了模塊中哪些功能是可用的,哪些是沒有相應實現的。
目前有效名稱為以下三個:posix
,nt
,java
。
其中posix
是 Portable Operating System Interface of UNIX(可移植操作系統接口)的縮寫。Linux 和 Mac OS 均會返回該值;nt
全稱應為“Microsoft Windows NT”,大體可以等同於 Windows 操作系統,因此 Windows 環境下會返回該值;java
則是 Java 虛擬機環境下的返回值。
因此在我的電腦(win10)上執行下述代碼,返回值是nt
:
>>> import os
>>> os.name
'nt'
而在 WSL(Windows Subsystem Linux,Windows 下的 Linux 子系統)上的結果則是:
>>> import os
>>> os.name
'posix'
查看
sys
模塊中的sys.platform
屬性可以得到關於運行平台更詳細的信息,在此不再贅述
2.2 os.environ
os.environ
屬性可以返回環境相關的信息,主要是各類環境變量。返回值是一個映射(類似字典類型),具體的值為第一次導入os
模塊時的快照;其中的各個鍵值對,鍵是環境變量名,值則是環境變量對應的值。在第一次導入os
模塊之后,除非直接修改os.environ
的值,否則該屬性的值不再發生變化。
比如其中鍵為“HOMEPATH”(Windows 下,Linux 下為“HOME”)的項,對應的值就是用戶主目錄的路徑。Windows 下,其值為:
>>> os.environ["HOMEPATH"]
'd:\\justdopython'
Linux 下,其值為:
>>> os.environ["HOME"]
'/home/justdopython'
2.3 os.walk()
這個函數需要傳入一個路徑作為top
參數,函數的作用是在以top
為根節點的目錄樹中游走,對樹中的每個目錄生成一個由(dirpath, dirnames, filenames)
三項組成的三元組。
其中,dirpath
是一個指示這個目錄路徑的字符串,dirnames
是一個dirpath
下子目錄名(除去“.”
和“..”
)組成的列表,filenames
則是由dirpath
下所有非目錄的文件名組成的列表。要注意的是,這些名稱並不包含所在路徑本身,要獲取dirpath
下某個文件或路徑從top
目錄開始的完整路徑,需要使用os.path.join(dirpath, name)
。
注意最終返回的結果是一個迭代器,我們可以使用for
語句逐個取得迭代器的每一項:
>>> for item in os.walk("."):
... print(item)
...
('.', ['do'], ['go_go_go.txt'])
('.\\do', ['IAmDirectory', 'python'], [])
('.\\do\\IAmDirectory', [], [])
('.\\do\\python', [], ['hello_justdopython.txt'])
2.4 os.listdir()
“listdir”即“list directories”,列出(當前)目錄下的全部路徑(及文件)。該函數存在一個參數,用以指定要列出子目錄的路徑,默認為“.”
,即“當前路徑”。
函數返回值是一個列表,其中各元素均為字符串,分別是各路徑名和文件名。
通常在需要遍歷某個文件夾中文件的場景下極為實用。
比如定義以下函數:
def get_filelists(file_dir='.'):
list_directory = os.listdir(file_dir)
filelists = []
for directory in list_directory:
# os.path 模塊稍后會講到
if(os.path.isfile(directory)):
filelists.append(directory)
return filelists
該函數的返回值就是當前目錄下所有文件而非文件夾的名稱列表。
2.5 os.mkdir()
“mkdir”,即“make directory”,用處是“新建一個路徑”。需要傳入一個類路徑參數用以指定新建路徑的位置和名稱,如果指定路徑已存在,則會拋出FileExistsError
異常。
該函數只能在已有的路徑下新建一級路徑,否則(即新建多級路徑)會拋出FileNotFoundError
異常。
相應地,在需要新建多級路徑的場景下,可以使用os.makedirs()
來完成任務。函數os.makedirs()
執行的是遞歸創建,若有必要,會分別新建指定路徑經過的中間路徑,直到最后創建出末端的“葉子路徑”。
示例如下:
>>> os.mkdir("test_os_mkdir")
>>> os.mkdir("test_os_mkdir")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileExistsError: [WinError 183] 當文件已存在時,無法創建該文件。: 'test_os_mkdir'
>>>
>>> os.mkdir("test_os_mkdir/test_os_makedirs/just/do/python/hello")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 3] 系統找不到指定的路徑。: 'test_os_mkdir/test_os_makedirs/just/do/python/hello'
>>>
>>> os.makedirs("test_os_mkdir/test_os_makedirs/just/do/python/hello")
2.6 os.remove()
用於刪除文件,如果指定路徑是目錄而非文件的話,就會拋出 IsADirectoryError
異常。刪除目錄應該使用os.rmdir()
函數。
同樣的,對應於os.makedirs()
,刪除路徑操作os.rmdir()
也有一個遞歸刪除的函數os.removedirs()
,該函數會嘗試從最下級目錄開始,逐級刪除指定的路徑,幾乎就是一個os.makedirs()
的逆過程;一旦遇到非空目錄即停止。
2.7 os.rename()
該函數的作用是將文件或路徑重命名,一般調用格式為os.rename(src, dst)
,即將src
指向的文件或路徑重命名為dst
指定的名稱。
注意,如果指定的目標路徑在其他目錄下,該函數還可實現文件或路徑的“剪切並粘貼”功能。但無論直接原地重命名還是“剪切粘貼”,中間路徑都必須要存在,否則就會拋出FileNotFoundError
異常。如果目標路徑已存在,Windows 下會拋出FileExistsError
異常;Linux 下,如果目標路徑為空且用戶權限允許,則會靜默覆蓋原路徑,否則拋出OSError
異常,
和上兩個函數一樣,該函數也有對應的遞歸版本os.renames()
,能夠創建缺失的中間路徑。
注意,這兩種情況下,如果函數執行成功,都會調用os.removedir()
函數來遞歸刪除源路徑的最下級目錄。
2.8 os.getcwd()
“getcwd”實際上是“get the current working directory”的簡寫,顧名思義,也就是說這個函數的作用是“獲取當前工作路徑”。在程序運行的過程中,無論物理上程序在實際存儲空間的什么地方,“當前工作路徑”即可認為是程序所在路徑;與之相關的“相對路徑”、“同目錄下模塊導入”等相關的操作均以“當前工作路徑”為准。
在交互式環境中,返回的就是交互終端打開的位置;而在 Python 文件中,返回的則是文件所在的位置。
在 Windows 下會有如下輸出:
>>> os.getcwd()
'd:\\justdopython\\just\\do\\python'
Linux 下的輸出則是:
>>> os.getcwd()
'/home/justdopython/just/do/python'
2.9 os.chdir()
“chdir”其實是“change the directory”的簡寫,因此os.chdir()
的用處實際上是切換當前工作路徑為指定路徑。其中“指定路徑”需要作為參數傳入函數os.chdir()
,該參數既可以是文本或字節型字符串,也可以是一個文件描述符,還可以是一個廣義的類路徑(path-like)對象。若指定路徑不存在,則會拋出FileNotFoundError
異常。
在 Windows 下,調用該函數的效果為:
>>> os.chdir("d:/justdopython/just/do")
>>> os.getcwd()
'd:\\justdopython\\just\\do'
在 Linux 下的效果則是:
>>> os.chdir("/home/justdopython/just/do") # 也可將參數指定為"..",即可切換到父目錄
>>> os.getcwd()
'/home/justdopython/just/do'
有了這個函數,跨目錄讀寫文件和調用模塊就會變得非常方便了,很多時候也就不必再反復將同一個文件在各個目錄之間復制粘貼運行,腳本完全可以坐鎮中軍,在一個目錄下完成對其他目錄文件的操作,正所謂“運籌帷幄之中,決勝於千里之外”也。
舉例來說,可以通過將“當前工作目錄”切換到父目錄,從而直接訪問父目錄的文件內容:
>>> os.chdir("..")
>>> os.getcwd()
'D:\\justdopython\\just'
>>> with open("hello_justdopython.txt", encoding="utf-8") as f:
... f.read()
...
'歡迎訪問 justdopython.com,一起學習 Python 技術~'
>>> os.listdir()
['hello_justdopython.txt']
3. os.path 模塊
其實這個模塊是os
模塊根據系統類型從另一個模塊導入的,並非直接由os
模塊實現,比如os.name
值為nt
,則在os
模塊中執行import ntpath as path
;如果os.name
值為posix
,則導入posixpath
。
使用該模塊要注意一個很重要的特性:os.path
中的函數基本上是純粹的字符串操作。換句話說,傳入該模塊函數的參數甚至不需要是一個有效路徑,該模塊也不會試圖訪問這個路徑,而僅僅是按照“路徑”的通用格式對字符串進行處理。
更進一步地說,os.path
模塊的功能我們都可以自己使用字符串操作手動實現,該模塊的作用是讓我們在實現相同功能的時候不必考慮具體的系統,尤其是不需要過多關注文件系統分隔符的問題。
3.1 os.path.join()
這是一個十分實用的函數,可以將多個傳入路徑組合為一個路徑。實際上是將傳入的幾個字符串用系統的分隔符連接起來,組合成一個新的字符串,所以一般的用法是將第一個參數作為父目錄,之后每一個參數即使下一級目錄,從而組合成一個新的符合邏輯的路徑。
但如果傳入路徑中存在一個“絕對路徑”格式的字符串,且這個字符串不是函數的第一個參數,那么其他在這個參數之前的所有參數都會被丟棄,余下的參數再進行組合。更准確地說,只有最后一個“絕對路徑”及其之后的參數才會體現在返回結果中。
>>> os.path.join("just", "do", "python", "dot", "com")
'just\\do\\python\\dot\\com'
>>>
>>> os.path.join("just", "do", "d:/", "python", "dot", "com")
'd:/python\\dot\\com'
>>>
>>> os.path.join("just", "do", "d:/", "python", "dot", "g:/", "com")
'g:/com'
3.2 os.path.abspath()
將傳入路徑規范化,返回一個相應的絕對路徑格式的字符串。
也就是說當傳入路徑符合“絕對路徑”的格式時,該函數僅僅將路徑分隔符替換為適應當前系統的字符,不做其他任何操作,並將結果返回。所謂“絕對路徑的格式”,其實指的就是一個字母加冒號,之后跟分隔符和字符串序列的格式:
>>> os.path.abspath("a:/just/do/python")
'a:\\just\\do\\python'
>>> # 我的系統中並沒有 a 盤
當指定的路徑不符合上述格式時,該函數會自動獲取當前工作路徑,並使用os.path.join()
函數將其與傳入的參數組合成為一個新的路徑字符串。示例如下:
>>> os.path.abspath("ityouknow")
'D:\\justdopython\\ityouknow'
3.3 os.path.basename()
該函數返回傳入路徑的“基名”,即傳入路徑的最下級目錄。
>>> os.path.basename("/ityouknow/justdopython/IAmBasename")
'IAmBasename'
>>> # 我的系統中同樣沒有這么一個路徑。可見 os.path.basename() 頁是單純進行字符串處理
整這個函數要注意的一點是,返回的“基名”實際上是傳入路徑最后一個分隔符之后的子字符串,也就是說,如果最下級目錄之后還有一個分隔符,得到的就會是一個空字符串:
>>> os.path.basename("/ityouknow/justdopython/IAmBasename/")
''
3.4 os.path.dirname()
與上一個函數正好相反,返回的是最后一個分隔符前的整個字符串:
>>> os.path.dirname("/ityouknow/justdopython/IAmBasename")
'/ityouknow/justdopython'
>>>
>>> os.path.dirname("/ityouknow/justdopython/IAmBasename/")
'/ityouknow/justdopython/IAmBasename'
3.5 os.path.split()
哈哈實際上前兩個函數都是弟弟,這個函數才是老大。
函數os.path.split()
的功能就是將傳入路徑以最后一個分隔符為界,分成兩個字符串,並打包成元組的形式返回;前兩個函數os.path.dirname()
和os.path.basename()
的返回值分別是函數os.path.split()
返回值的第一個、第二個元素。就連二者的具體實現都十分真實:
def basename(p):
"""Returns the final component of a pathname"""
return split(p)[1]
def dirname(p):
"""Returns the directory component of a pathname"""
return split(p)[0]
通過os.path.join()
函數又可以把它們組合起來得到原先的路徑。
3.6 os.path.exists()
這個函數用於判斷路徑所指向的位置是否存在。若存在則返回True
,不存在則返回False
:
>>> os.path.exists(".")
True
>>> os.path.exists("./just")
True
>>> os.path.exists("./Inexistence") # 不存在的路徑
False
一般的用法是在需要持久化保存某些數據的場景,為避免重復創建某個文件,需要在寫入前用該函數檢測一下相應文件是否存在,若不存在則新建,若存在則在文件內容之后增加新的內容。
3.7 os.path.isabs()
該函數判斷傳入路徑是否是絕對路徑,若是則返回True
,否則返回False
。當然,僅僅是檢測格式,同樣不對其有效性進行任何核驗:
>>> os.path.isabs("a:/justdopython")
True
3.8 os.path.isfile() 和 os.path.isdir()
這兩個函數分別判斷傳入路徑是否是文件或路徑,注意,此處會核驗路徑的有效性,如果是無效路徑將會持續返回False
。
>>> # 無效路徑
>>> os.path.isfile("a:/justdopython")
False
>>>
>>> # 有效路徑
>>> os.path.isfile("./just/plain_txt")
True
>>>
>>> # 無效路徑
>>> os.path.isdir("a:/justdopython/")
False
>>> # 有效路徑
>>> os.path.isdir("./just/")
True
4. 總結
本文詳細介紹了與操作系統交互的os
模塊中一些常用的屬性和函數,基本可以覆蓋初階的學習和使用。有了這些功能,我們已經可以寫出一些比較實用的腳本了。
除了文中介紹的函數外,os
模塊還有很多更加復雜的功能,但大多是我們暫時用不到的,以后用到會進一步講解。
示例代碼:python-100-days
5. 參考資料
關注公眾號:python技術,回復"python"一起學習交流