1. 程式人生 > >【練習題】第十四章--檔案(Think Python)

【練習題】第十四章--檔案(Think Python)

2.讀寫檔案

 

要寫入一個檔案,就必須要在開啟它的時候用『w』作為第二個引數(譯者注:w 就是 wirte 的意思了):

>>> fout = open('output.txt', 'w')

如果檔案已經存在了,這樣用寫入的模式來開啟,會把舊的檔案都清除掉,然後重新寫入檔案,所以一定要小心!如果檔案不存在,程式就會建立一個新的。

open 函式會返回一個檔案物件,檔案物件會提供各種方法來處理檔案。write 這個方法就把資料寫入到檔案中了。

>>> line1 = "This here's the wattle,\n"
>>> fout.write(line1)
24

返回值是已寫入字元的數量。檔案物件會記錄所在位置,所以如果你再次呼叫write方法,會從檔案結尾的地方繼續新增新的內容。

>>> line2 = "the emblem of our land.\n"
>>> fout.write(line2)
24

寫完檔案之後,你需要用 close 方法來關閉檔案。

>>> fout.close()

如果不 close 這個檔案,就要等你的程式執行結束退出的時候,它自己才關閉了。

3.格式運算子

>>> camels = 42
>>> '%d' % camels
'42'

如果格式化序列有一個以上了,那麼第二個引數就必須是一個元組了。每個格式序列對應元組當中的一個元素,次序相同。

下面的例子中,用了'%d'來格式化輸出整型值,用'%g'來格式化浮點數,'%s'就是給字串用的了。

>>> 'In %d years I have spotted %g %s.' % (3, 0.1, 'camels')
'In 3 years I have spotted 0.1 camels.'

這就要注意力,如果字串中格式化序列有多個,那個數一定要和後面的元組中元素數量相等才行。另外格式化序列與元組中元素的型別也必須一樣。

4.檔名與路徑

>>> import os
>>> cwd = os.getcwd()
>>> cwd
'/home/dinsdale'
>>> os.path.abspath('memo.txt')
 '/home/dinsdale/memo.txt'
>>> os.path.exists('memo.txt')
True
>>> os.path.isdir('memo.txt')
False
>>> os.path.isdir('/home/dinsdale')
True
>>> os.listdir(cwd)
['music', 'photos', 'memo.txt']

為了展示一下這些函式的用法,下面這個例子中,walks 這個函式就遍歷了一個目錄,然後輸出了所有該目錄下的檔案的名字,並且在該目錄下的所有子目錄中遞迴呼叫自身。

def walk(dirname):
    for name in os.listdir(dirname):
        path = os.path.join(dirname, name)
        if os.path.isfile(path):
            print(path)
        else:
            walk(path)

 5.捕獲異常

try:
    fin = open('bad_file')
except:
    print('Something went wrong.')

Python 會先執行 try 後面的語句。如果執行正常,就會跳過 except 語句,然後繼續執行。如果除了異常,就會跳出 try 語句,然後執行 except 語句中的程式碼。

這種用 try 語句來處理異常的方法,就叫異常捕獲。上面的例子中,except 語句中的輸出資訊並沒有什麼用。一般情況,得到異常之後,你可以選擇解決掉這個問題或者再重試一下,或者就以正常狀態退出程式了。

6.資料庫

資料庫是一個用來管理已儲存資料的檔案。很多資料庫都以類似字典的形式來管理資料,就是從鍵到鍵值成對對映。資料庫和字典的最大區別就在於資料庫是儲存在磁碟(或者其他永久性儲存裝置中),所以程式執行結束退出後,資料庫依然存在。

(譯者注:這裡作者為了便於理解,對資料庫的概念進行了極度的簡化,實際上資料庫的型別、模式、功能等等都與字典有很大不同,比如有關係型資料庫和非關係型資料庫,還有分散式的和單一檔案式的等等。如果有興趣對資料庫進行進一步瞭解,譯者推薦一本書:SQLite Python Tutorial。)

dbm 模組提供了一個建立和更新資料庫檔案的互動介面。下面這個例子中,我建立了一個數據庫,其中的內容是影象檔案的標題。

開啟資料庫檔案就跟開啟其他檔案差不多:

>>> import dbm
>>> db = dbm.open('captions', 'c')

後面這個 c 是一個模式,意思是如果該資料庫不存在就建立一個新的。得到的返回結果就是一個數據庫物件了,用起來很多的運算都跟字典很像。

建立一個新的項的時候,dbm 就會對資料庫檔案進行更新了。

>>> db['cleese.png'] = 'Photo of John Cleese.'

讀取裡面的某一項的時候,dbm 就讀取資料庫檔案:

>>>db['cleese.png']
b'Photo of John Cleese.'

上面的程式碼返回的結果是一個二進位制物件,這也就是開頭有個 b 的原因了。二進位制物件就跟字串在很多方面都挺像的。以後對 Python 的學習深入了之後,這種區別就變得很重要了,不過現在還不要緊,咱們就忽略掉。

如果對一個已經存在值的鍵進行賦值,dbm 就會把舊的值替換成新的值:

>>> db['cleese.png'] = 'Photo of John Cleese doing a silly walk.'
>>> db['cleese.png']
b'Photo of John Cleese doing a silly walk.'

字典的一些方法,比如 keys 和 items,是不能用於資料庫物件的。但用一個 for 迴圈來迭代是可以的:

for key in db:
    print(key, db[key])

然後就同其他檔案一樣,用完了之後你得用 close 方法關閉資料庫:

>>> db.close()

 7.Pickle模組

dbm 的侷限就在於鍵和鍵值必須是字串或者二進位制。如果用其他型別資料,就得到錯誤了。

這時候就可以用 pickle 模組了。該模組可以把幾乎所有型別的物件翻譯成字串模式,以便儲存在資料庫中,然後用的時候還可以把字串再翻譯回來。

pickle.dumps 接收一個物件做引數,然後返回一個字串形式的內容翻譯(dumps 就是『dump string』的縮寫):

>>> import pickle
>>> t = [1, 2, 3]
>>> pickle.dumps(t)
b'\x80\x03]q\x00(K\x01K\x02K\x03e.'

這種格式讓人讀起來挺複雜;這種設計能讓 pickle 模組解譯起來比較容易。pickle.lods("load string")就又會把原來的物件解譯出來:

>>> t1 = [1, 2, 3]
>>> s = pickle.dumps(t1)
>>> t2 = pickle.loads(s)
>>> t2
[1, 2, 3]

這裡要注意了,新的物件與舊的有一樣的值,但(通常)並不是同一個物件:

>>> t1 == t2
True
>>> t1 is t2
False

換句話說,就是說 pickle 解譯的過程就如同複製了原有物件一樣。

有 pickle了,就可以把非字串的資料也存到資料庫裡面了。實際上這種結合方式特別普遍,已經封裝到一個叫shelve的模組中了。

8.管道

大多數作業系統都提供了一個命令列介面,也被稱作『shell』。Shell 通常提供了很多基礎的命令,能夠來搜尋檔案系統,以及啟動應用軟體。比如,在 Unix 下面,就可以通過 cd 命令來切換目錄,用 ls 命令來顯示一個目錄下的內容,如果裝了火狐瀏覽器,就可以輸入 fireforx 來啟動瀏覽器了。

在 shell 下能夠啟動的所有程式,也都可以在 Python 中啟動,這要用到一個 pipe 物件,這個直接翻譯意思為管道的物件可以理解為 Python 到作業系統的 Shell 進行通訊的途徑,一個 pipe 物件就代表了一個執行的程式。

舉個例子吧,Unix 的 ls -l 命令通常會用長檔名格式來顯示當前目錄的內容。在 Python 中就可以用 os.open 來啟動它:

>>> cmd = 'ls -l'
>>> fp = os.popen(cmd)

引數 cmd 是包含了 shell 命令的一個字串。返回的結果是一個物件,用起來就像是一個打開了的檔案一樣。

可以讀取ls 程序的輸出,用 readline 的話每次讀取一行,用 read 的話就一次性全部讀取:

>>> res = fp.read()

用完之後要關閉,這點也跟檔案一樣:

>>> stat = fp.close()
>>> print(stat)
None

返回值是 ls 這個程序的最終狀態;None 的意思就是正常退出(沒有錯誤)。

舉個例子,大多數 Unix 系統都提供了一個教唆 md5sum 的函式,會讀取一個檔案的內容,然後計算一個『checksum』(校驗值)。你可以點選這裡閱讀更多相關內容。

這個命令可以很有效地檢查兩個檔案是否有相同內容。兩個不同內容產生同樣的校驗值的可能性是很小的(實際上在宇宙坍塌之前都沒戲)。

你就可以用一個 pipe 來從 Python 啟動執行 md5sum,然後獲取結果:

>>> filename = 'book.tex'
>>> cmd = 'md5sum ' + filename
>>> fp = os.popen(cmd)
>>> res = fp.read()
>>> stat = fp.close()
>>> print(res)
1e0033f0ed0656636de0d75144ba32e0  book.tex
>>> print(stat)
None

9.編寫模組

任何包含 Python 程式碼的檔案都可以作為模組被匯入使用。舉個例子,假設你有一個名字叫 wc.py 的檔案,裡面程式碼如下:

def linecount(filename):
    count = 0
    for line in open(filename):
        count += 1
    return count
print(linecount('wc.py'))

如果執行這個程式,程式就會讀取自己本身,然後輸出檔案中的行數,也就是7行了。你還可以匯入這個模組,如下所示:

>>> import wc
7

現在你就有一個模組物件 wc 了:

>>> wc
<module 'wc' from 'wc.py'>

該模組提供了數行數的函式linecount:

>>> wc.linecount('wc.py')
7

你看,你就可以這樣來為 Python 寫模組了。

當然這個例子中有個小問題,就是匯入模組的時候,模組內程式碼在最後一行對自身進行了測試。

一般情況你匯入一個模組,模組只是定義了新的函式,但不會去主動執行自己內部的函式。

以模組方式匯入使用的程式一般用下面這樣的慣用形式:

if __name__ == '__main__':
    print(linecount('wc.py'))

name 是一個內建變數,當程式開始執行的時候被設定。如果程式是作為指令碼來執行的,name 的值就是'main';這樣的話,if條件滿足,測試程式碼就會執行。而如果該程式碼被用作模組匯入了,if 條件不滿足,測試的程式碼就不會運行了。

10.除錯

讀寫檔案的時候,你可能會碰到空格導致的問題。這些問題很難解決,因為空格、跳錶以及換行,平常就難以用眼睛看出來:

>>> s = '1 2\t 3\n 4'
>>> print(s)
1 2  3
4

這時候就可以用內建函式 repr 來幫忙。它接收任意物件作為引數,然後返回一個該物件的字串表示。對於字串,該函式可以把空格字元轉成反斜槓序列:

>>> print(repr(s))
'1 2\t 3\n 4'

該函式的功能對除錯來說很有幫助。

另外一個問題就是不同作業系統可能用不同字元表示行尾。

有的用一個換行符,也就是\n。有的用一個返回字元,也就是\r。有的兩個都虧。如果你把檔案在不同作業系統只見移動,這種不相容性就可能導致問題了。

對大多數作業系統,都有一些應用軟體來進行格式轉換。你可以在https://en.wikipedia.org/wiki/Newline查詢一下(並且閱讀關於該問題的更多細節)。當然,你也可以自己寫一個轉換工具了。

練習1:

寫一個函式,名為 sed,接收一個目標字串,一個替換字串,然後兩個檔名;讀取第一個檔案,然後把內容寫入到第二個檔案中,如果第二個檔案不存在,就建立一個。如果目標字串在檔案中出現了,就用替換字串把它替換掉。

如果在開啟、讀取、寫入或者關閉檔案的時候發生了錯誤了,你的程式應該要捕獲異常,然後輸出錯誤資訊,然後再退出。

def sed(s1,s2,f1,f2):
    try:       
        fin=open(f1,'r')
        fout=open(f2,'w')
    except:
        print('openError!')
    for s in fin:
        try:
            if(s.strip()==s1):
                fout.write(s2+'\r\n')
            else:
                fout.write(s)
        except:
            print('writeError!')
    fin.close()
    fout.close()

def main():
    s1='aa'
    s2='bbb'
    f1='words.txt'
    f2='words2.txt'
    sed(s1,s2,f1,f2)

if __name__=='__main__':
    main()

練習2:

如果你從 這裡下載了我的樣例程式碼,你會發現該程式建立了一個字典,建立了從一個有序字母字串到一個單詞列表的對映,列表中的單詞可以由這些字母拼成。例如'opst'就對映到了列表 [’opts’, ’post’, ’pots’, ’spot’, ’stop’, ’tops’].

寫一個模組,匯入 anagram_sets 然後提供兩個函式:store_anagrams 可以把相同字母異序詞詞典儲存到一個『shelf』;read_anagrams 可以查詢一個詞,返回一個由其 相同字母異序詞 組成的列表。