文件輸入/輸出
數(shù)據(jù)持久化最簡單的類型是普通文件粪般,有時也叫平面文件(?at ?le)。它僅僅是在一個文件 名下的字節(jié)流闸拿,把數(shù)據(jù)從一個文件讀入內存空盼,然后從內存寫入文件。Python 很容易實現(xiàn)這 些文件操作新荤,它模仿熟悉的和流行的 Unix 系統(tǒng)的操作揽趾。
讀寫一個文件之前需要打開它:
fileobj = open(filename,mode)
下面時對該open()調用的簡單解釋:
- fileobj時open()返回的文件對象苛骨。
- filename是該文件的字符串名
- mode是指明文件類型和操作的字符串
mode 的第一個字母表示對其的操作篱瞎。
- r 表示讀模式
- w 表示寫模式,如果文件不存在則新創(chuàng)建痒芝,如果存在則重寫新內容
- x 表示文件不存在的情況下新創(chuàng)建并寫入文件
- a 表示如果文件存在俐筋,在文件末尾追加寫內容
mode 的第二個字母是文件類型1:
- t (或者省略)代表文本類型
- b 代表二進制文件
打開文件之后就可以調用函數(shù)來讀寫數(shù)據(jù)。
最后需要關閉文件严衬。
接下來我們會在一個程序中用Python字符串創(chuàng)建一個文件澄者,然后返回。
使用write()寫文本文件
首先創(chuàng)建我們使用的文本源:
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()
函數(shù)write()返回寫入文件的字節(jié)數(shù)。和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() 默認會在每個參數(shù)后 面添加空格,在每行結束處添加換行屈留。 在之前的例子中局冰,它在文件 relativity 中默認添 加了一個換行。為了使 print() 與 write() 有同樣的輸出灌危,傳入下面兩個參數(shù)康二。
- sep分隔符:默認是一個空格 ' '
- end結束字符:默認是一個換行符'\n'
除非自定義參數(shù),否則print()會使用默認參數(shù)勇蝙。在這里沫勿,我們通過空字符串替換print()添加對的所有多余輸出:
In [19]: fout = open('relativity','wt',sep=' ',end=' ')
In [20]: print(poem,file=fout)
In [21]: fout.close()
上面的場景,打開文件就清晰很多味混,在未設置換行時候产雹,最后一行是沒有換行的。
如果源字符串非常大翁锡,可以將數(shù)據(jù)分塊蔓挖,直到所有字符被寫入:
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()讀文本文件
我們可以使用不帶參數(shù)的read()函數(shù)一次讀入文件的所有內容角溃。
In [17]: fin = open('relativity','rt')
In [18]: poem = fin.read()
In [19]: fin.close()
同樣也可以設置最大的讀入字符數(shù)限制 read() 函數(shù)一次返回的大小拷获。下面一次讀入 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 的循環(huán)未蝌。 當然驮吱,我們也能使用 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毕匀。函數(shù)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'癌别,那么文件會以二進制模式打開皂岔。這種情況下,讀寫的是字節(jié)而不是字符串展姐。
我們首先生成一串二進制數(shù)據(jù):
In [25]: bdata = bytes(range(0,256))
In [26]: len(bdata)
Out[26]: 256
以二進制模式打開躁垛,并且一次寫入所有的數(shù)據(jù):
In [27]: fout = open('bfile','wb')
In [28]: fout.write(bdata)
Out[28]: 256
write()返回寫入的字節(jié)數(shù)
In [29]: fout.close()
對于文本文件,也可以分塊寫二進制數(shù)據(jù)圾笨。
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 會關掉此文 件。這也就意味著在一個函數(shù)中打開文件擂达,沒有及時關閉它土铺,但是在函數(shù)結束時會被關 掉。然而你可能會在一直運行中的函數(shù)或者程序的主要部分打開一個文件板鬓,應該強制剩下 的所有寫操作完成后再關閉文件悲敷。
Python的上下文管理器(context manager)會清理一些資源,例如打開的文件俭令。它的形式為 with expression as variable:
In [42]: with open('withfile','wt') as fout:
...: fout.write(poem)
...:
完成上下文管理器的代碼后后德,文件會自動關閉
使用seek()改變位置
無論是讀或者寫文件,Python都會跟蹤文件中的位置抄腔。函數(shù)tell()返回距離文件開始處的字節(jié)偏移量探遵。函數(shù)seek()允許跳轉到其他字節(jié)偏移量的位置。這意味著可以不用從頭讀取文件的每一個字節(jié)妓柜,直接跳轉到最后位置并只讀一個字節(jié)也是可以的箱季。
下面就是我們的栗子:
使用之前的二進制文件bfile
In [43]: fin = open('bfile','rb')
In [44]: fin.tell()
Out[44]: 0
使用 seek() 讀取文件結束前最后一個字節(jié):
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()函數(shù)的時候,可以使用兩個參數(shù):seek(offset,origin)棍掐。
- 如果origin等于(默認為0)藏雏,從開頭便宜offset個字節(jié)
- 如果origin等于1,從當前位置偏移offset個字節(jié)
- 如果origin等于2作煌,距離最后結束處偏移offset個字節(jié)
這些值也在標準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
我們可以用不同的方法讀取最后一個字節(jié):
In [9]: fin = open('bfile','rb')
結尾前的一個字節(jié):
In [10]: fin.seek(-1,2)
Out[10]: 255
In [11]: fin.tell()
Out[11]: 255
在調用seek()函數(shù)時不需要額外調用tell()掘殴。上面只是說明兩個函數(shù)具有相同的偏移量
一直讀到結尾:
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')
下面的栗子返回最后兩個字節(jié):
In [16]: fin.seek(254,0)
Out[16]: 254
In [17]: fin.tell()
Out[17]: 254
在此基礎上前進一個字節(jié):
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
這些函數(shù)對于二進制文件都是極其重要的。當文件是 ASCII 編碼(每個字符一個字節(jié)) 時粟誓,也可以使用它們奏寨,但是計算偏移量會是一件麻煩事。其實鹰服,這些都取決于文本的編碼 格式病瞳,最流行的編碼格式(例如 UTF-8)每個字符的字節(jié)數(shù)都不盡相同揽咕。
注:本文內容來自《Python語言及其應用》歡迎購買原書閱讀