文件輸入/輸出
數據持久化最簡單的類型是普通文件,有時也叫平面文件(?at ?le)。它僅僅是在一個文件 名下的字節流,把數據從一個文件讀入內存,然后從內存寫入文件。Python 很容易實現這 些文件操作,它模仿熟悉的和流行的 Unix 系統的操作。
讀寫一個文件之前需要打開它:
fileobj = open(filename,mode)
下面時對該open()調用的簡單解釋:
- fileobj時open()返回的文件對象。
- filename是該文件的字符串名
- mode是指明文件類型和操作的字符串
mode 的第一個字母表示對其的操作。
- r 表示讀模式
- w 表示寫模式,如果文件不存在則新創建,如果存在則重寫新內容
- x 表示文件不存在的情況下新創建并寫入文件
- a 表示如果文件存在,在文件末尾追加寫內容
mode 的第二個字母是文件類型1:
- t (或者省略)代表文本類型
- b 代表二進制文件
打開文件之后就可以調用函數來讀寫數據。
最后需要關閉文件。
接下來我們會在一個程序中用Python字符串創建一個文件,然后返回。
使用write()寫文本文件
首先創建我們使用的文本源:
In [4]: poem = '''There was a young lady named Bright,
...: Whose speed was far faster than light;
...: She started one day
...: In a relative way,
...: And returned on the previous night.'''
In [5]: len(poem)
Out[5]: 154
將文本源添加到文件'relativity' 中:
In [6]: fout = open('relativity','wt')
In [7]: fout.write(poem)
In [8]: fout.close()
函數write()返回寫入文件的字節數。和print()一樣,它沒有增加空格或換行符。同樣,我們也可以在一個文本文件中使用print()。
In [15]: fout = open
In [16]: fout = open('relativity','wt')
In [17]: print(poem,file=fout)
In [18]: fout.close()
這就產生了一個問題:到底是使用 write() 還是 print() ? print() 默認會在每個參數后 面添加空格,在每行結束處添加換行。 在之前的例子中,它在文件 relativity 中默認添 加了一個換行。為了使 print() 與 write() 有同樣的輸出,傳入下面兩個參數。
- sep分隔符:默認是一個空格 ' '
- end結束字符:默認是一個換行符'\n'
除非自定義參數,否則print()會使用默認參數。在這里,我們通過空字符串替換print()添加對的所有多余輸出:
In [19]: fout = open('relativity','wt',sep=' ',end=' ')
In [20]: print(poem,file=fout)
In [21]: fout.close()
上面的場景,打開文件就清晰很多,在未設置換行時候,最后一行是沒有換行的。
如果源字符串非常大,可以將數據分塊,直到所有字符被寫入:
In [19]: fout = open('relativity','wt')
In [20]: size = len(poem)
In [21]: offset = 0
In [22]: chunk = 100
In [23]: while True:
...: if offset > size:
...: break
...: fout.write(poem[offset:offset+chunk])
...: offset += chunk
...:
In [24]: fout.close()
第一次寫入100個字符,然后寫入剩下的50個字符。
如果文件 'relativity' 已經存在,使用模式 x 可以避免重寫文件:
In [25]: fout = open('relativity','xt')
Traceback (most recent call last): File "<stdin>", line 1, in <module> FileExistsError: [Errno 17] File exists: 'relativity'
可以添加一個異常處理:
In [27]: try:
...: fout = open('relativity', 'xt')
...: fout.write('stomp stomp stomp')
...: except:
...: print('relativity already exists!. That was a close one.')
...:
relativity already exists!. That was a close one.
使用read(),readline()或者readlines()讀文本文件
我們可以使用不帶參數的read()函數一次讀入文件的所有內容。
In [17]: fin = open('relativity','rt')
In [18]: poem = fin.read()
In [19]: fin.close()
同樣也可以設置最大的讀入字符數限制 read() 函數一次返回的大小。下面一次讀入 100 個 字符,然后把每一塊拼接成原來的字符串 poem:
In [40]: fin = open('relativity','rt')
In [41]: while True:
...: fragment = fin.read(chunk)
...: if not fragment:
...: break
...: poem += fragment
...:
In [42]: fin.close()
讀到文件結尾之后,再次調用 read() 會返回空字符串(''),if not fragment 條件被判為 False。此時會跳出 while True 的循環。 當然,我們也能使用 readline() 每次讀入文件的一 行。在下面栗子中,通過追加每一行拼接成原來的字符串 poem:
In [5]: poem = ''
In [6]: fin = open('relativity','rt')
In [7]: while True:
...: line = fin.readline()
...: if not line:
...: break
...: poem += line
...:
In [8]: fin.close()
對于一個文本文件,即使空行也有1字符長度(換行字符 '\n'),自然就會返回True。當 文件讀取結束后,readline()(類似 read())同樣會返回空字符串,也被 while True 判 為 False。
In [9]: poem = ''
In [10]: fin = open('relativity','rt')
In [11]: for line in fin:
...: poem += line
...:
In [12]: fin.close()
前面所有的栗子最終都返回單個字符串poem。函數readlines()調用時每次讀取一行,并且返回單行字符串的列表:
In [19]: fin = open('relativity','rt')
In [20]: lines = fin.readlines()
In [21]: print(len(lines),'lines read')
5 lines read
In [22]: for line in lines:
...: print(line,end='')
...:
使用write()寫二進制文件
如果文件模式字符串中包含'b',那么文件會以二進制模式打開。這種情況下,讀寫的是字節而不是字符串。
我們首先生成一串二進制數據:
In [25]: bdata = bytes(range(0,256))
In [26]: len(bdata)
Out[26]: 256
以二進制模式打開,并且一次寫入所有的數據:
In [27]: fout = open('bfile','wb')
In [28]: fout.write(bdata)
Out[28]: 256
write()返回寫入的字節數
In [29]: fout.close()
對于文本文件,也可以分塊寫二進制數據。
In [30]: fout = open('bfile','wb')
In [31]: size = len(bdata)
In [32]: chunk = 100
In [33]: offset = 0
In [34]: while True:
...: if offset > size:
...: break
...: fout.write(bdata[offset:offset+chunk])
...: offset += chunk
...:
In [35]: fout.close()
使用read()讀取二進制文件
下面的栗子只需要用'rb'打開文件即可:
In [38]: fin = open('bfile','rb')
In [39]: bdata = fin.read()
In [40]: len(bdata)
Out[40]: 256
In [41]: fin.close()
使用with自動關閉文件
如果你忘記關閉已經打開的一個文件, 在該文件對象不再被引用之后 Python 會關掉此文 件。這也就意味著在一個函數中打開文件,沒有及時關閉它,但是在函數結束時會被關 掉。然而你可能會在一直運行中的函數或者程序的主要部分打開一個文件,應該強制剩下 的所有寫操作完成后再關閉文件。
Python的上下文管理器(context manager)會清理一些資源,例如打開的文件。它的形式為 with expression as variable:
In [42]: with open('withfile','wt') as fout:
...: fout.write(poem)
...:
完成上下文管理器的代碼后,文件會自動關閉
使用seek()改變位置
無論是讀或者寫文件,Python都會跟蹤文件中的位置。函數tell()返回距離文件開始處的字節偏移量。函數seek()允許跳轉到其他字節偏移量的位置。這意味著可以不用從頭讀取文件的每一個字節,直接跳轉到最后位置并只讀一個字節也是可以的。
下面就是我們的栗子:
使用之前的二進制文件bfile
In [43]: fin = open('bfile','rb')
In [44]: fin.tell()
Out[44]: 0
使用 seek() 讀取文件結束前最后一個字節:
In [45]: fin.seek(255)
Out[45]: 255
一直讀到文件結束:
In [46]: bdata = fin.read()
In [47]: len(bdata)
Out[47]: 1
In [48]: bdata[0]
Out[48]: 255
調用seek()函數的時候,可以使用兩個參數:seek(offset,origin)。
- 如果origin等于(默認為0),從開頭便宜offset個字節
- 如果origin等于1,從當前位置偏移offset個字節
- 如果origin等于2,距離最后結束處偏移offset個字節
這些值也在標準os模塊中被定義:
In [1]: import os
In [2]: os.SEEK_SET
Out[2]: 0
In [3]: os.SEEK_CUR
Out[3]: 1
In [4]: os.SEEK_END
Out[4]: 2
我們可以用不同的方法讀取最后一個字節:
In [9]: fin = open('bfile','rb')
結尾前的一個字節:
In [10]: fin.seek(-1,2)
Out[10]: 255
In [11]: fin.tell()
Out[11]: 255
在調用seek()函數時不需要額外調用tell()。上面只是說明兩個函數具有相同的偏移量
一直讀到結尾:
In [12]: bdata = fin.read()
In [13]: len(bdata)
Out[13]: 1
In [14]: bdata[0]
Out[14]: 255
下面時從文件的當前位置尋找的栗子:
In [15]: fin = open('bfile','rb')
下面的栗子返回最后兩個字節:
In [16]: fin.seek(254,0)
Out[16]: 254
In [17]: fin.tell()
Out[17]: 254
在此基礎上前進一個字節:
In [18]: fin.seek(1,1)
Out[18]: 255
In [19]: fin.tell()
Out[19]: 255
最后一直讀到文件結尾:
In [20]: bdata = fin.read()
In [21]: len(bdata)
Out[21]: 1
In [22]: bdata[0]
Out[22]: 255
這些函數對于二進制文件都是極其重要的。當文件是 ASCII 編碼(每個字符一個字節) 時,也可以使用它們,但是計算偏移量會是一件麻煩事。其實,這些都取決于文本的編碼 格式,最流行的編碼格式(例如 UTF-8)每個字符的字節數都不盡相同。
注:本文內容來自《Python語言及其應用》歡迎購買原書閱讀