2018年2月19日 星期一

Python 學習筆記 : 檔案與目錄處理

前陣子在河堤新書展示櫃看到下面這本碁峰出的 "Python 初學特訓班 (增訂版)", 翻閱發現此書除了前面是適合初學者的 Python ABC 之外, 還包括了許多功能與實用技巧的介紹, 例如網路爬蟲, Matplotlib 圖表繪製, Facebook 貼文與照片下載, Youtube 影片下載, 甚至 OpenCV 臉部辨識 ... 等等, 可以說 Python 的重要應用都含括進來了, 真是一本初學加應用的好書.


Source : 金石堂


另外一本去年買的 "Python 程式設計實務 (博碩, 何敏煌)" 也是類似基礎 + 應用的好書 :


Source : 金石堂


還有還參考了一本借自母校高科大的 "精通 Python" :


Source : 金石堂


今年一定要攻克 Python 這關, 掃除進取機器學習的障礙. 雖然之前在研究 MicroPython on ESP8266 時已經對 Python 基礎做過大致整理, 但畢竟 MicroPython 只是 Python 3 的精簡版, 特別是檔案處理部分不過應景而已, 與樹莓派上的全功能 Python 有不小差距.

那就從 Python 檔案處理著手測試吧! Python 提供下列套件來操作檔案與目錄 :


 檔案處理套件 說明
 os 建立或刪除目錄, 刪除檔案, 執行作業系統命令
 os.path 處理檔案路徑與名稱, 檢查檔案或路徑是否存在, 計算檔案大小
 os.walk 搜尋指定目錄與其子目錄, 以遞迴方式取得目錄中所有檔案與下一層目錄
 shutil 可複製, 移動, 刪除檔案與目錄之跨平台套件
 glob 支援以萬用字元 * 取得指定條件之檔案串列


一. 用 os.path 套件取得檔案與目錄資訊 :

os.path 套件提供下列方法從作業系統取得檔案或目錄資訊, 使用前須用 import os.path 匯入 :

import os.path

其方法摘要如下表 :


 os.path 套件方法 說明
 abspath(x) 傳回指定之檔案或目錄 x 之絕對路徑名稱
 basename(x) 傳回路徑名稱 x 的最後一個名稱 (檔案或目錄)
 dirname(x) 傳回路徑名稱 x 的上層完整路徑名稱
 getsize(x) 傳回指定檔案 x 的大小 (byte)
 split(x) 將絕對路徑 x 中的檔案與上層目錄分開後以 tuple 傳回 (可取出檔名)
 splitdrive(x) 將絕對路徑 x 中的磁碟機與上層目錄分開後以 tuple 傳回 (可取出磁碟機)
 join(x, y) 將路徑 x 與檔案名稱 y 結合成完整絕對路徑後傳回
 exists(x) 檢查指定之目錄或檔案 x 是否存在
 isabs(x) 檢查指定之路徑 x 是否為完整之絕對路徑名稱
 isfile(x) 檢查指定之路徑 x 是否為檔案
 isdir(x) 檢查指定之路徑 x 是否為目錄


方法 abspath() 會傳回指定檔案或目錄之絕對路徑, 而 isabs() 則用來檢查傳入之路徑是否為絕對路徑, 例如我在 D 碟下有一個 test 目錄與一個 main.py 檔案, 而 test 下又有一個 test 目錄 :

D:\test
    |---- test\
    |---- main.py

D:\test>python
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 18:41:36) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os.path
>>> abs=os.path.abspath("main.py")   #傳回絕對路徑字串
>>> abs
'D:\\test\\main.py'
>>> os.path.isabs(abs)                           #檢查是否為絕對路徑 (是)
True
>>> os.path.isabs("main.py")                 #檢查是否為絕對路徑 (否)
False

在樹莓派的 pi 使用者目錄下有一個 lastip.txt 檔案與 test 目錄, test 目錄下又還有一個 test 目錄, 例如 :

/home/pi
     |-----laspip.txt
     |-----test
               |----test

pi@raspberrypi:~ $ python3
Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os.path
>>> abs=os.path.abspath("lastip.txt")
>>> abs
'/home/pi/lastip.txt'
>>> os.path.isabs(abs)
True
>>> os.path.isabs("lastip.txt")
False

方法 basename() 會傳回一個絕對路徑中的最後名稱 (即檔案或目錄名稱), 而方法 dirname() 則是傳回一個絕對路徑中的上層目錄路徑 (即父目錄路徑), 方法 isfile() 與 isdir() 分別檢查傳入之路徑為檔案還是目錄, 例如 :

>>> import os.path
>>> abs=os.path.abspath("main.py")
>>> abs
'D:\\test\\main.py'
>>> file=os.path.basename(abs)    #傳回檔案或目錄之絕對路徑
>>> file
'main.py'
>>> os.path.isfile(file)                   #檢查是否為檔案 (是)
True
>>> os.path.isdir(file)                    #檢查是否為目錄 (否)
False
>>> dir=os.path.dirname(abs)        #傳回上層目錄路徑
>>> dir
'D:\\test'
>>> abs=os.path.abspath("test")     #傳回檔案或目錄之絕對路徑
>>> abs
'D:\\test\\test'
>>> dir=os.path.dirname(abs)         #傳回上層目錄路徑
>>> dir
'D:\\test'
>>> os.path.isdir(dir)                       #檢查是否為目錄 (是)
True

在樹莓派的 Raspbian 測試結果如下 :

>>> import os.path
>>> abs=os.path.abspath("lastip.txt")
>>> abs
'/home/pi/lastip.txt'
>>> file=os.path.basename(abs)
>>> file
'lastip.txt'
>>> os.path.isfile(file)
True
>>> os.path.isdir(file)
False
>>> dir=os.path.dirname(abs)
>>> dir
'/home/pi'
>>> abs=os.path.abspath("test")
>>> abs
'/home/pi/test'
>>> dir=os.path.dirname(abs)         #上一層目錄路徑
>>> dir
'/home/pi'
>>> os.path.isdir(dir)
True
>>> os.path.isfile(dir)
False

目前目錄 "." 與上一層目錄 ".." 也可以用 isdir() 檢測, 在 Windows 與 Linux 用法相同, 例如 :

>>> os.path.isdir(".")
True
>>> os.path.isdir("..")
True
>>> os.path.isfile(".")
False
>>> os.path.isfile("..")
False

方法 splitdrive() 會將傳入之絕對路徑拆成磁碟機與路徑, 傳回此兩字串組成之 tuple; 方法 join() 則可將這兩部分組合成絕對路徑, 例如在 Windows 下測試 :

>>> abs=os.path.abspath("main.py")          #傳回絕對路徑字串
>>> drive, path=os.path.splitdrive(abs)    #將絕對路徑拆成磁碟機與路徑
>>> drive
'D:'
>>> path
'\\test\\main.py'
>>> os.path.join(drive, path)      #將磁碟機與路徑組合成絕對路徑
'D:\\test\\main.py'

樹莓派的測試結果 :

>>> import os.path
>>> abs=os.path.abspath("lastip.txt")
>>> abs
'/home/pi/lastip.txt'
>>> drive, path=os.path.splitdrive(abs)
>>> drive         
''                                 #Linux 磁碟機為空字串
>>> path
'/home/pi/lastip.txt'

可見在 Linux 系統裡, splidrive() 傳回的磁碟機是空字串, 書中範例是 MacOS 結果一樣.

還有一個在檔案目錄處理最常用的 exists(), 此方法用來檢測傳入之檔案或目錄是否存在, 傳入參數可以是檔案或目錄名稱, 也可以是絕對路徑, 當傳入參數為檔案或目錄名稱時, 表示此為目前目錄下之檔案或子目錄. 例如 :

>>> import os.path
>>> os.path.exists("main.py")     #目前目錄下有 main.py 故傳回 True
True
>>> os.path.exists("xyz.py")        #目前目錄下沒有 xyz.py 故傳回 False
False
>>> abs=os.path.abspath("main.py")
>>> os.path.exists(abs)                 #傳入檔案絕對路徑
True


二. 用 os 套件操作檔案與目錄  :

os 套件中有下列方法可用來建立或刪除目錄, 刪除檔案, 以及執行作業系統命令 (不能建立檔案), 使用前須先用 import os 匯入 :


 os 套件方法 說明
 listdir(d)  顯示目錄 d 底下的檔案與子目錄
 mkdir(d) 建立目錄 d, 須搭配 os.path.exists() 檢查目錄是否存在以免產生執行錯誤
 rmdir(d) 刪除目錄 d, 須搭配 os.path.exists() 檢查目錄是否存在以免產生執行錯誤
 chdir(d) 切換工作目錄至目錄 d
 getcwd() 傳回目前的工作目錄
 remove(f) 刪除檔案 f, 須搭配 os.path.exists() 檢查檔案是否存在以免產生執行錯誤
 rename(old, new) 將舊目錄或檔案名稱 old 改成新目錄或檔案名稱
 system(c) 執行作業系統命令 c


注意, 不論是建立或刪除目錄, 刪除檔案都應先以 os.path.exists() 方法檢查目錄或檔案是否已存在以避免產生執行錯誤, 例如下列指令會在目前目錄 D:\test 下建立 kkk 子目錄 :

>>> import os
>>> os.mkdir("kkk")

這時若再執行一次 os.mkdir("kkk") 指令就會出現錯誤, 因為 kkk 子目錄已經存在 :

>>> os.mkdir("kkk")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileExistsError: [WinError 183] 當檔案已存在時,無法建立該檔案。: 'kkk'

正確方式為先用 os.path.exists() 方法檢查目錄是否已存在 :

>>> import os
>>> dir="kkk"
>>> if not os.path.exists(dir):
...     os.mkdir(dir)
... else:
...     print(dir + "已經建立")
...
kkk已經建立

刪除目錄時也是一樣要先檢查是否已存在, 例如 :

>>> import os
>>> dir="kkk"
>>> if os.path.exists(dir):
...     os.rmdir(dir)
...     print(dir + " 已刪除")
... else:
...     print(dir + " 不存在")
...
kkk 已刪除
>>> os.rmdir(dir)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 2] 系統找不到指定的檔案。: 'kkk'

可見刪除目錄後直接用 os.rmdir() 再刪除一次就會出現 FileNotFoundError 錯誤. 

同樣地, 刪除檔案時也要先檢查是否存在, 例如 :

>>> import os
>>> file="test.txt"
>>> if os.path.exists(file):
...     os.remove(file)
...     print(file + " 已刪除")
... else:
...     print(file + " 不存在")
...
test.txt 已刪除
>>> os.remove(file)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
FileNotFoundError: [WinError 2] 系統找不到指定的檔案。: 'test.txt'

可見成功刪除 test.txt 後直接用 os.remove() 再刪除一次就會出現 FileNotFoundError 錯誤. os 套件中沒有建立檔案的函數, 建立檔案使用 Python 內建函數 Open().

方法 os.rename() 可用來更改檔案或目錄名稱, 例如 :

>>> import os
>>> os.path.exists("test.txt")         #改名前 test.txt 存在
True
>>> os.path.exists("tt.txt")            #改名前 tt.txt 不存在
False
>>> os.rename("test.txt","tt.txt")   #將 test.txt 改名為 tt.txt
>>> os.path.exists("test.txt")          #改名後 test.txt 已不存在
False
>>> os.path.exists("tt.txt")             #改名後 tt.txt 存在
True

最後 os.system() 函數用來呼叫執行作業系統命令列指令, 例如在 Windows 系統中使用 os.system() 呼叫 mkdir 可建立目錄, 呼叫 cls 可清除螢幕等, 測試如下 :

>>> os.system("mkdir test")    #系統指令正常執行傳回 0
0
>>> os.system("clear")            #系統指令未正常執行傳回 1
'clear' 不是內部或外部命令、可執行的程式或批次檔。
1                       

傳入的系統指令若正確會在執行完畢後傳回 0, 否則傳回 1.


三. 用 shutil 處理檔案與目錄  :

shutil 套件為一跨平台檔案處理套件, 使用前需用 import shutil 匯入. 它提供如下之高階檔案與目錄處理方法 :

 shutil 套件方法 說明
 copyfile(s, d) 複製檔案 s 至檔案 d (僅內容, 不包括屬性)
 copy(s, d) 複製檔案 s 至檔案 d (包括權限屬性)
 copy2(s, d) 複製檔案 s 至檔案 d (包括全部屬性)
 copytree(s, d) 將目錄 s 以及其下所有檔案與子目錄複製到 d
 rmtree(d) 刪除目錄 d 以及其下所有檔案與子目錄
 move(s, d) 將目錄或檔案 s 搬移為 d

其中複製檔案有僅複製內容的 copyfile(); 有可複製權限之 copy(); 以及可複製全部屬性的 copy2(); 這三個檔案複製方法都會傳回目的檔案名稱, 例如 :

>>> import shutil
>>> shutil.copyfile("t111.txt", "u222.log")    #複製 t111.txt 內容為 u222.log
'u222.log'
>>> shutil.copy("t111.txt", "v333")               #複製 t111.txt 內容與權限屬性為 v333
'v333'
>>> shutil.copy2("t111.txt", "w444.txt")       #複製 t111.txt 內容與全部屬性為 w444.txt
'w444.txt'
>>> shutil.move("w444.txt", "w444")           #將 w444.txt 改為 w444
'w444'

為了測試 copytree() 與 rmtree() 方法, 先用 os.mkdir() 建立一個目錄 x555 :

>>> import os
>>> os.mkdir("x555")                            #建立空目錄 x555
>>> shutil.copytree("x555", "y666")     #複製為 y666, 傳回目的目錄 y666
'y666'
>>> shutil.rmtree("y666")                      #刪除目錄 y666


四. 用 glob.glob 處理檔案列表  :

glob 是一個外部套件, 其用途是取得指定路徑下的檔案列表 (注意, 只有檔案, 不包括目錄), 使用前先用 import glob 匯入, 然後呼叫 glob.glob(pathname) 方法, 傳入指定絕對或相對路徑 pathname 與檔案匹配規則, 會傳回一個符合該規則之檔案串列, 例如在 Windows 我的 D 碟 test 目錄下有如下的檔案與目錄 :

D:\test
    |---- kkk
              |----t111.txt
              |----u222.log
              |----v333

glob.glob() 可傳入目前目錄 D:\test 下的子目錄路徑, 例如 kkk, 但那樣只會傳回目錄 kkk 本身而已, 必須加上檔案匹配規則才會列出檔案, 匹配規則可以使用類似正規表示法中的 * 或 ? 等通配符號, * 表示任何數目之任何字元, 而 ? 表示單一字元. 也可以使用 [abc] 來表示含有 a, b, 或 c 字元之檔案名稱, 或者 [0-9], [a-z], [A-Z] 等表示含有數字或英文字母之檔名, 例如 :

>>> import glob
>>> glob.glob("kkk")                     #沒有檔案匹配規則, 只傳回 kkk 子目錄而已
['kkk']           
>>> glob.glob("kkk/*")                  #用 * 傳回 kkk 子目錄下全部檔案
['kkk\\t111.txt', 'kkk\\u222.log', 'kkk\\v333']
>>> glob.glob("kkk/*.*")               #用 *.* 只列出副檔名含有小數點者
['kkk\\t111.txt', 'kkk\\u222.log']
>>> glob.glob("kkk/*.l??")             #用 ? 只匹配單一字元
['kkk\\u222.log']
>>> glob.glob("kkk/t*")                       
['kkk\\t111.txt']

不過這些匹配方式只是 UNIX shell 的匹配規則而已, 並未支援完整的正規表示法.

以上範例目錄分隔符號使用的是右斜線, 而傳回之檔案串列中的路徑分隔符號則為兩個左斜線. 在 Windows 下 glob.glob() 之傳入參數也可以用左斜線, 但必須使用兩個, 否則會傳回空串列, 例如 :

>>> glob.glob("kkk\t*")                  #Windows 下使用單一左斜線會傳回空串列
[]
>>> glob.glob("kkk\\t*")                 #Windows 下須使用兩個左斜線     
['kkk\\t111.txt']

但在 Linux 中傳入之路徑分隔符號則一律是右斜線, 例如在樹莓派中我也建立與上面 Windows 一樣之檔案結構 :

/home/pi/test
    |---- kkk
              |----t111.txt
              |----u222.log
              |----v333

pi@raspberrypi:~/test $ python3
Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import glob
>>> glob.glob("kkk")
['kkk']
>>> glob.glob("kkk/*.*")
['kkk/t111.txt', 'kkk/u222.log']
>>> glob.glob("kkk/*")
['kkk/t111.txt', 'kkk/v333', 'kkk/u222.log']
>>> glob.glob("kkk/*.l??")
['kkk/u222.log']
>>> glob.glob("kkk/t*")
['kkk/t111.txt']
>>> glob.glob("kkk\t*")       #Linux 中路徑分隔符不可用左斜線
[]
>>> glob.glob("kkk\\t*")      #Linux 中路徑分隔符不可用左斜線
[]

可見在 Linux 中傳回之檔案串列都是使用右斜線當路徑分隔符號.

除了使用相對路徑外, 也可以在 glob.glob() 中傳入絕對位址, 這須使用 os.path.abspath() 方法, 例如 :

>>> import os.path
>>> dir=os.path.abspath("kkk")    #傳回 kkk 子目錄絕對位址
>>> dir
'D:\\test\\kkk'
>>> glob.glob(dir)                         #沒有檔案匹配規則, 只傳回 kkk 子目錄而已
['D:\\test\\kkk']
>>> glob.glob("kkk/*")                 #相對位址
['kkk\\t111.txt', 'kkk\\t222.txt']
>>> glob.glob(dir + "/*")               #字串串接路徑與檔案匹配規則 (絕對位址)
['D:\\test\\kkk\\t111.txt', 'D:\\test\\kkk\\t222.txt']

可見, 若使用絕對位址, 則傳回值也是絕對位址.

參考 :

簡單掌握Python中glob模塊查找文件路徑的用法


五. 用 os.walk() 取得檔案目錄之樹狀結構  :

os.walk(p) 方法可以用來搜尋指定目錄, 傳入參數 p 為一個目錄之絕對路徑, 它會傳回一個具有三個元素之串列 [dir_name, subdir_name, files] :

dir_name : 目前目錄名稱

~待續

沒有留言 :