歡迎光臨
每天分享高質量文章

用Python實現磁碟IO操作全攻略,讓資料流動起來!

導讀:IO在計算機中指的是Input/Output,也就是輸入輸出。凡是用到資料交換的地方,都會涉及IO程式設計,例如磁碟、網路的資料傳輸。

在IO程式設計中,Stream(流)是一種重要的概念,分為輸入流(Input Stream)和輸出流(Output Stream)。我們可以把流理解為一個水管,資料相當於水管中的水,但是隻能單向流動,所以資料傳輸過程中需要架設兩個水管,一個負責輸入,一個負責輸出,這樣讀寫就可以實現同步。

本文主要講解磁碟IO操作。

 

 

作者:範傳輝

如需轉載請聯絡大資料(ID:hzdashuju)

 

 

 

01 檔案讀寫

1. 開啟檔案

讀寫檔案是最常見的IO操作。Python內建了讀寫檔案的函式,方便了檔案的IO操作。

檔案讀寫之前需要開啟檔案,確定檔案的讀寫樣式。open函式用來開啟檔案,語法如下:

open(name[.mode[.buffering]])

open函式使用一個檔案名作為唯一的強制引數,然後傳回一個檔案物件。樣式(mode)和緩衝區(buffering)引數都是可選的,預設樣式是讀樣式,預設緩衝區是無。

假設有個名為qiye.txt的文字檔案,其儲存路徑是c:\text(或者是在Linux下的~/text),那麼可以像下麵這樣開啟檔案。在互動式環境的提示符“>>>”下,輸入如下內容:

>>> f = open(r'c:\text\qiye.txt')

如果檔案不存在,將會看到一個類似下麵的異常回溯:

 

Traceback (most recent call last):
    File "", line 1in 
IOError: [Errno 2] No such file or directory: 'C:\\qiye.txt'

2. 檔案樣式

下麵主要說一下open函式中的mode引數,透過改變mode引數可以實現對檔案的不同操作。

  • ‘r’:讀樣式

  • ‘w’:寫樣式

  • ‘a’:追加樣式

  • ‘b’:二進位制樣式(可新增到其他樣式中使用)

  • ‘+’:讀/寫樣式(可新增到其他樣式中使用)

這裡主要是提醒一下’b’引數的使用,一般處理文字檔案時,是用不到’b’引數的,但處理一些其他型別的檔案(二進位制檔案),比如mp3音樂或者影象,那麼應該在樣式引數中增加’b’,這在爬蟲中處理媒體檔案很常用。引數’rb’可以用來讀取一個二進位制檔案。

3. 檔案緩衝區

open函式中第三個可選引數buffering控制著檔案的緩衝。

 

如果引數是0,I/O操作就是無緩衝的,直接將資料寫到硬碟上;如果引數是1,I/O操作就是有緩衝的,資料先寫到記憶體裡,只有使用flush函式或者close函式才會將資料更新到硬碟;如果引數為大於1的數字則代表緩衝區的大小(單位是位元組),-1(或者是任何負數)代表使用預設緩衝區的大小。

4. 檔案讀取

檔案讀取主要是分為按位元組讀取和按行進行讀取,經常用到的方法有read()、readlines()、close()。

在“>>>”輸入f = open(r’c:\text\qiye.txt’)後,如果成功開啟文字檔案,接下來呼叫read()方法則可以一次性將檔案內容全部讀到記憶體中,最後傳回的是str型別的物件:

>>> f.read()
"qiye"

最後一步呼叫close(),可以關閉對檔案的取用。檔案使用完畢後必須關閉,因為檔案物件會佔用作業系統資源,影響系統的IO操作。

>>> f.close()

由於檔案操作可能會出現IO異常,一旦出現IO異常,後面的close()方法就不會呼叫。所以為了保證程式的健壯性,我們需要使用try … finally來實現。

try:
    f = open(r'c:\text\qiye.txt','r')
    print f.read()
finally:
    if f:
        f.close()

上面的程式碼略長,Python提供了一種簡單的寫法,使用with陳述句來替代try … finally程式碼塊和close()方法,如下所示:

with open(r'c:\text\qiye.txt','r'as fileReader:
    print fileReader.read()

呼叫read()一次將檔案內容讀到記憶體,但是如果檔案過大,將會出現記憶體不足的問題。一般對於大檔案,可以反覆呼叫read(size)方法,一次最多讀取size個位元組。如果檔案是文字檔案,Python提供了更加合理的做法,呼叫readline()可以每次讀取一行內容,呼叫readlines()一次讀取所有內容並按行傳回串列。

 

大家可以根據自己的具體需求採取不同的讀取方式,例如小檔案可以直接採取read()方法讀到記憶體,大檔案更加安全的方式是連續呼叫read(size),而對於配置檔案等文字檔案,使用readline()方法更加合理。

將上面的程式碼進行修改,採用readline()的方式實現如下所示:

with open(r'c:\text\qiye.txt','r'as fileReader:
    for line in fileReader.readlines():
        print line.strip()

5. 檔案寫入

寫檔案和讀檔案是一樣的,唯一的區別是在呼叫open方法時,傳入識別符號’w’或者’wb’表示寫入文字檔案或者寫入二進位制檔案,示例如下:

f = open(r'c:\text\qiye.txt','w')
f.write('qiye')
f.close()

我們可以反覆呼叫write()方法寫入檔案,最後必須使用close()方法來關閉檔案。使用write()方法的時候,作業系統不是立即將資料寫入檔案中的,而是先寫入記憶體中快取起來,等到空閑時候再寫入檔案中,最後使用close()方法就將資料完整地寫入檔案中了。

 

當然也可以使用f.flush()方法,不斷將資料立即寫入檔案中,最後使用close()方法來關閉檔案。和讀檔案同樣道理,檔案操作中可能會出現IO異常,所以還是推薦使用with陳述句:

with open(r'c:\text\qiye.txt','w'as fileWriter:
    fileWriter.write('qiye')

02 操作檔案和目錄

在Python中對檔案和目錄的操作經常用到os模組和shutil模組。接下來主要介紹一些操作檔案和目錄的常用方法:

  • 獲得當前Python指令碼工作的目錄路徑:

    os.getcwd()。

  • 傳回指定目錄下的所有檔案和目錄名:

    os.listdir()。例如傳回C盤下的檔案:os.listdir(“C:\\”)

  • 刪除一個檔案:

    os.remove(filepath)。

  • 刪除多個空目錄:

    os.removedirs(r”d:\python”)。

  • 檢驗給出的路徑是否是一個檔案:

    os.path.isfile(filepath)。

  • 檢驗給出的路徑是否是一個目錄:

    os.path.isdir(filepath)。

  • 判斷是否是絕對路徑:

    os.path.isabs()。

  • 檢驗路徑是否真的存在:

    os.path.exists()。例如檢測D盤下是否有Python檔案夾:os.path.exists(r”d:\python”)

  • 分離一個路徑的目錄名和檔案名:

    os.path.split()。例如:os.path.split(r”/home/qiye/qiye.txt”),傳回結果是一個元組:(‘/home/qiye’, ‘qiye.txt’)。

  • 分離副檔名:

    os.path.splitext()。例如os.path.splitext(r”/home/qiye/qiye.txt”),傳回結果是一個元組:(‘/home/qiye/qiye’, ‘.txt’)。

  • 獲取路徑名:

    os.path.dirname(filetpah)。

  • 獲取檔案名:

    os.path.basename(filepath)。

  • 讀取和設定環境變數:

    os.getenv()與os.putenv()。

  • 給出當前平臺使用的行終止符:

    os.linesep。Windows使用’\r\n’,Linux使用’\n’而Mac使用’\r’。

  • 指示你正在使用的平臺:

    os.name。對於Windows,它是’nt’,而對於Linux/Unix使用者,它是’posix’。

  • 重新命名檔案或者目錄:

    os.rename(old,new)。

  • 建立多級目錄:

    os.makedirs(r”c:\python\test”)。

  • 建立單個目錄:

    os.mkdir(“test”)。

  • 獲取檔案屬性:

    os.stat(file)。

  • 修改檔案許可權與時間戳:

    os.chmod(file)。

  • 獲取檔案大小:

    os.path.getsize(filename)。

  • 複製檔案夾:

    shutil.copytree(“olddir”,”newdir”)。olddir和newdir都只能是目錄,且newdir必須不存在。

  • 複製檔案:

    shutil.copyfile(“oldfile”,”newfile”),oldfile和newfile都只能是檔案;shutil. copy(“oldfile”,”newfile”),oldfile只能是檔案,newfile可以是檔案,也可以是標的目錄。

  • 移動檔案(目錄):

    shutil.move(“oldpos”,”newpos”)。

  • 刪除目錄:

    os.rmdir(“dir”),只能刪除空目錄;shutil.rmtree(“dir”),空目錄、有內容的目錄都可以刪。

03 序列化操作

物件的序列化在很多高階程式語言中都有相應的實現,Python也不例外。程式執行時,所有的變數都是在記憶體中的,例如在程式中宣告一個dict物件,裡面儲存著爬取的頁面的連結、頁面的標題、頁面的摘要等資訊:

d = dict(url='index.html',title='首頁',content='首頁')

在程式執行的過程中爬取的頁面的連結會不斷變化,比如把url改成了second.html,但是程式一結束或意外中斷,程式中的記憶體變數都會被作業系統進行回收。

 

如果沒有把修改過的url儲存起來,下次執行程式的時候,url被初始化為index.html,又是從首頁開始,這是我們不願意看到的。所以把記憶體中的變數變成可儲存或可傳輸的過程,就是序列化。

將記憶體中的變數序列化之後,可以把序列化後的內容寫入磁碟,或者透過網路傳輸到別的機器上,實現程式狀態的儲存和共享。反過來,把變數內容從序列化的物件重新讀取到記憶體,稱為反序列化。

在Python中提供了兩個模組:cPickle和pickle來實現序列化,前者是由C語言編寫的,效率比後者高很多,但是兩個模組的功能是一樣的。一般編寫程式的時候,採取的方案是先匯入cPickle模組,如果此模組不存在,再匯入pickle模組。示例如下:

try:
    import cPickle as pickle
except ImportError:
    import pickle

pickle實現序列化主要使用的是dumps方法或dump方法。dumps方法可以將任意物件序列化成一個str,然後可以將這個str寫入檔案進行儲存。在Python Shell中示例如下:

>>> import cPickle as pickle
>>> d = dict(url='index.html',title='首頁',content='首頁')
>>> pickle.dumps(d)
"(dp1\nS'content'\np2\nS'\\xca\\xd7\\xd2\\xb3'\np3\nsS'url'\np4\nS'index.html'\np5\nsS'title'\np6\ng3\ns."

如果使用dump方法,可以將序列化後的物件直接寫入檔案中:

>>> f=open(r'D:\dump.txt','wb')
>>> pickle.dump(d,f)
>>> f.close()

pickle實現反序列化使用的是loads方法或load方法。把序列化後的檔案從磁碟上讀取為一個str,然後使用loads方法將這個str反序列化為物件,或者直接使用load方法將檔案直接反序列化為物件,如下所示:

 

>>> f=open(r'D:\dump.txt','rb')
>>> d=pickle.load(f)
>>> f.close()
>>> d
{'content''\xca\xd7\xd2\xb3''url''index.html''title''\xca\xd7\xd2\xb3'}

透過反序列化,儲存為檔案的dict物件,又重新恢復出來,但是這個變數和原變數沒有什麼關係,只是內容一樣。以上就是序列化操作的整個過程。

假如我們想在不同的程式語言之間傳遞物件,把物件序列化為標準格式是關鍵,例如XML,但是現在更加流行的是序列化為JSON格式,既可以被所有的程式語言讀取解析,也可以方便地儲存到磁碟或者透過網路傳輸。

 

關於作者:範傳輝,資深網蟲,Python開發者,參與開發了多項網路應用,在實際開發中積累了豐富的實戰經驗,並善於總結,貢獻了多篇技術文章廣受好評。研究興趣是網路安全、爬蟲技術、資料分析、驅動開發等技術。

本文摘編自《Python爬蟲開發與專案實戰》,經出版方授權釋出。

延伸閱讀《Python爬蟲開發與專案實戰

點選上圖瞭解及購買

轉載請聯絡微信:DoctorData

推薦語:零基礎學習爬蟲技術,從Python和Web前端基礎開始講起,由淺入深,包含大量案例,實用性強。

贊(0)

分享創造快樂