Python — 使用檔案系統

Sean Yeh
Python Everywhere -from Beginner to Advanced
11 min readJun 9, 2020

先前我們提過,想要「將檔案儲存到磁碟機裡面」時,大致上可以分成兩個部分:首先,需要從作業系統的檔案總管(在Mac OS上面成為Finder )來回移動我們的路徑與資料夾(目錄),找到我們想要的檔案。接著我們需要開啟檔案,讀取檔案的內容或著是把內容寫入該檔案,讀寫完畢之後,我們還要進行儲存,並且關閉檔案。檔案開啟與關閉之間的各種操作,先前已經提過,這一篇,要說明前半部,也就是關於檔案系統的各種操作方式。

檔案系統

當程式在執行的時候,你可以透過使用變數來存放資料。但是如果希望資料在程式結束後還可以保存起來的話,就需要將資料存到檔案中。檔案有兩個性質:檔名與路徑。檔名告知存放資料檔案的名稱與類型、路徑則指示了檔案在電腦中的位置。

檔案系統包含如何指定檔案的路徑、重新命名檔案與目錄、建立檔案或者是移動檔案。基本上來說,就是我們在Windows的檔案總管(或者是Mac電腦的Finder)中的各種操作方式。

要在Python中使用檔案系統的話,一般會使用os與os.path模組中的函式。最近也有人使用pathlib函式來操作檔案系統。

路徑與路徑名稱

幾乎所有的作業系統都是以樹狀結構來建立路徑的。磁碟機是整體樹狀結構的「根」,資料夾與子資料夾則像是主樹幹、次數幹、主樹枝、次樹枝一樣,沿著根成樹枝狀一路展開。樹狀結構的根為根目錄,樹狀結構的樹幹、樹枝分別為目錄、子目錄。

不同的作業系統對路徑的語法有不同的規定,在Linux與Mac OS中,採用斜線(/)符號來分隔目錄與子目錄,而在Windows中,則是使用反斜線(\)符號來分隔。此外,在Linux中只有單一的根目錄;而在Windows中,每一台磁碟機都是一個獨立的根目錄(例如:A:\ 、C:\ 、D:\等)。

如果想要讓所有作業系統中都能執行,在程式設計時就要處理這兩種狀況。

幸運的是,Python提供了相關函式以方便程式來指定檔案路徑,而不必擔心系統的差異。例如使用os.path.join()函式來處理,os.path.join()會返回檔案路徑的字串,而且會以系統正確的分隔符號來分割資料夾和檔名。可以在不同的作業系統中試試下面的程式碼:

import osos.path.join('product','men','clothes')

絕對路徑與相對路徑

作業系統允許路徑有兩種指定方式:「絕對路徑」是從根目錄開始。而「相對路徑」是檔案相對於檔案系統某一個參考點的位置,若參考點改變,最後指到的位置就會不一樣。那麼要如何取得「參考點」的位置?有兩種方式可以達到:

第一種方式是將相對路徑附加到絕對路徑的後面,從而產生新的絕對路徑。

另種是將目前工作目錄當作參考點。那麼要如何取得「目前工作目錄」?

os.getcwd() — 目前工作目錄

目前工作目錄(current working directory),可以簡稱為cwd。可以利用os.getcwd()函式取得目前工作目錄,並且可以用os.chdir()來切換變更目錄。你可以試試下面的程式碼:

import osos.getcwd()os.chdir('../')os.getcwd()

os.listdir() — 路徑下所有檔案清單

可以取得指定路徑下所有子目錄與檔案的清單。透過 os.curdir 作為 os.listdir() 的參數,可以取得目前工作目錄裡面的清單。

os.listdir(os.curdir)

glob模組

在使用os.listdir的時候,可能會發現路徑中有一些檔案並不是你想要處理的檔案。這時候,可以使用glob模組中的glob函式來進行篩選。glob函式會回傳目前工作目錄中與條件符合的檔案。

import glob
glob.glob(path_name)

glob.glob(path_name),這裡的 path_name可以是絕對路徑,也可以是相對路徑。

我們可以用萬用字元(*)加上其他的字串,取出想要的檔案。萬用字元可以對應任何數量的任何字元。如下,我們可以找到資料夾裡面所有副檔名為csv的檔案。

glob.glob("*.csv")

os.path — 處理路徑名稱

os.path模組裡面有很多與檔案名稱和檔案的路徑有關的函式。因為os.path是os模組裡面的模組,只需要import os到py檔案裡面就可以使用了。我們可以使用os.path來取得路徑名稱。

如果要建立路徑的話,可以使用os.path.join()。記得我們在前面曾經提過os.path會依照目前正在使用的作業系統以系統正確的分隔符號來分割資料夾和檔名。無論是使用哪一個作業系統,os.path.join()都不會檢查參數是否合法。因此在使用時,最好是自己寫一個函式來檢查路徑的合法性。

# os.path.join()

前面已經提過,os.path.join()會返回檔案路徑的字串,而且以系統正確的分隔符號來分隔資料夾與檔名。此外,如果想要為檔案建立字串的話,使用os.path.join()會相對方便:

files = ["index.html","main.css","main.js"]for filename in files:    print(os.path.join("new_project", filename))

# 與os.curdir搭配使用

我們可以用下面方式取得目前工作目錄下面的特定資料夾:下面程式碼可以取得目前工作目錄下面csv資料夾中的所有檔案名稱。

url = os.path.join(os.curdir,'csv')
os.listdir(url)

# os.path.basename(path)

basename是指路徑最後的目錄或檔案名稱。使用os.path.basename(),會擷取路徑最後的資料夾或檔案名稱。

os.path.basename(filr_url)

承上,這個程式碼會取得的結果是csv。因為csv是最後的目錄。

# os.path.split(path)

會將basename與路徑的其他部分分開,並且以tuple的型態傳回。

# os.path.dirname(path)

會傳回路徑中不包括basename的部分。

# os.path.splitext(path)

可以取得檔案的副檔名,並且以tuple的型態傳回,第一個元素是路徑第二個元素是副檔名。

# os.path.abspath(path)

如果你只知道相對路徑,可以透過呼叫os.path.abspath()取得絕對路徑。這是將相對路徑轉換為絕對路徑的一個簡單方法。

# os.path.isabs(path)

如果path是絕對路徑的話,就會返回True,反之如果path是相對路徑的話,,就會返回False。

import osa = os.path.join('product','men','clothes','index.html')aa = os.path.split(a)
ab =os.path.basename(a)
ac = os.path.dirname(a)
ad = os.path.splitext(a)
print(a)
print('split: '+str(aa))
print('basename: '+ab)
print('dirname: '+ac)
print('splitext: '+str(ad))

執行後的結果:

product/men/clothes/index.htmlsplit: ('product/men/clothes', 'index.html')basename: index.htmldirname: product/men/clothessplitext: ('product/men/clothes/index', '.html')

建立資料夾(目錄)

可以利用os.makedirs()函式來建立資料夾。例如:

os.makedirs("new_project")

上面的程式將會建立一個new_project資料夾。

如果是下面的程式碼,則會建立一個new_project資料夾與一個html子資料夾:

os.makedirs("new_project/html")

如果資料夾已經存在的話,該如何?由於makedirs的預設屬性是False,因此若資料夾已經存在,會顯示錯誤訊息。反之,如果想要覆蓋已存在的資料夾,可以加上 exist_ok = True 參數。

os.makedirs("new_project/html", exist_ok = True)

值得注意的是,使用os.makedirs()(複數)或os.mkdir() (單數)都可以建立新目錄。這兩個函式的差別在於os.mkdir()(單數)不會自動建立多層目錄,而os.makedirs()(複數)可以建立多層目錄。

其他操作

os.rename() — 重新命名檔案

os.rename() 可以重新命名檔案或目錄。

os.remove() — 刪除檔案

os.remove() 可以刪除檔案但是不可以刪除資料夾。

os.rmdir() — 刪除資料夾(目錄)

os.rmdir()可以刪除資料夾,條件是該資料夾必須是空白資料夾。如果想要刪除空白資料夾,可以使用shutil.rmtree()函式。

檔案與資料夾的複製

shutil模組可以幫助你在Python程式中搬移、複製、修改檔名或刪除檔案。使用前,要先匯入shutil模組。

import shutil

複製

shutil.copy(src_file, des)可以將來源路徑的檔案(src_file)複製到目的路徑(des)的資料夾裡面。

shutil.copy(src_file,des)

實例:檔案備份程式

我們試著來做一個檔案備份程式吧。

情境:我們想要備份一個檔案到備份資料夾中。並且在不改變原始檔案的狀態下重新命名備份檔案,在檔案後面加上備份日期(年、月、日)並且在檔案中間加上一個bk字樣,用以識別。

首先需要匯入shutil、os 與 datetime

import shutil, osimport datetime

在開始之前,先移動到作用資料夾。WORK_PATH是想要備份檔案的位置。我們可以使用chdir()來移動到這個位置。

WORK_PATH = os.path.join('files','source','data')# 移動位置os.chdir(WORK_PATH)

接著,我們可以把程式寫成一個function以供日後呼叫。取名為backup_file,並且放入兩個參數。

第一個是來源檔案名稱(src_file),第二個參數是備份檔案目的地(des)。

首先,使用os.path.splitext()來分離出檔案名稱與副檔名。由於分離出來後,會存在一個tuple裡面,所以我們可以使用f[0]來取出檔案名稱,f[1]來取出副檔名。並且指定一個變數 bk_file,把新檔名格式組起來,裡面包含「bk」與時間 mytime()。

bk_file = f[0]+"_bk_"+mytime()+f[1]

到這裡,你會發現時間 mytime()也是一個function的原因,是我想把格式化時間這件事情從主程式中出離出來。這樣子如果日後想要改變時間的格式,會比較方便調整。下面就是mytime function:

def mytime():    now = datetime.date.today()    return str(now)

從中間可以發現,我們取得今天的時間(.today()),並且轉變資料型態為字串後,在傳出來。

我們再回到backup_file function:在前面我們已經知道bk_file是存放未來備份檔案格式的變數。

還記得 shutil.copy(src_file,des)的參數有兩個。其中,第二個是目的地路徑與檔名。為了產生第二個參數需要的條件,我們再指定一個backup變數,利用os.path.join()來把目的地路徑與檔名組合起來。

backup = os.path.join(des, bk_file)

最後,再把backup放入shutil.copy()的第二個參數中。

shutil.copy(src_file,backup)

backup_file function的全部如下:

def backup_file(src_file, des):    f =os.path.splitext(src_file)    bk_file = f[0]+"_bk_"+mytime()+f[1]    backup = os.path.join(des,bk_file)    shutil.copy(src_file,backup)

完成function後,我們就可以用下面方式來呼叫函式使用。

backup_file('sample.csv', 'backup_folder')

其中,第一個參數放入原始檔名,第二個參數放入備份資料夾名稱。

--

--

Sean Yeh
Python Everywhere -from Beginner to Advanced

# Taipei, Internet Digital Advertising,透過寫作讓我們回想過去、理解現在並思考未來。並樂於分享,這才是最大贏家。