給Python學(xué)習(xí)者的文件讀寫指南(含基礎(chǔ)與進(jìn)階)
對于初學(xué)者來說,一份詳盡又清晰明白的指南很重要。今天,貓貓跟大家一起,好好學(xué)習(xí)Python文件讀寫的內(nèi)容,這部分內(nèi)容特別常用,掌握后對工作和實(shí)戰(zhàn)都大有益處。學(xué)習(xí)是循序漸進(jìn)的過程,欲速則不達(dá)。文章較長,建議大家收藏,以備復(fù)習(xí)查閱哦。
1、如何將列表數(shù)據(jù)寫入文件?
2、如何從文件中讀取內(nèi)容?
3、多樣需求的讀寫任務(wù)
4、從with語句到上下文管理器
如何將列表數(shù)據(jù)寫入文件?
首先,我們來看看下面這段代碼,并思考:這段代碼有沒有問題,如果有問題的話,要怎么改?
li = ['python',' is',' a',' cat'] with open('test.txt','w') as f: f.write(li)
現(xiàn)在公布答案,這段代碼會(huì)報(bào)錯(cuò):
TypeError Traceback (most recent call last)
<ipython-input-6-57e0c2f5a453> in <module>()
1 with open('test.txt','w') as f:
----> 2 f.write(li)TypeError: write() argument must be str, not list
以上代碼的想法是將list列表內(nèi)容寫入txt文件中,但是報(bào)錯(cuò) TypeError: write() argument must be str。就是說,write()方法必須接受字符串(str)類型的參數(shù)。
Python中內(nèi)置了str()方法,可以返回字符串版本的對象(Return a string version of object)。所以,上面的例子中,我們試試把 f.write(li) 改為 f.write(str(li)) ,先做一下字符串類型的轉(zhuǎn)化看看。代碼略。
這次沒有報(bào)錯(cuò)了,但是打開文件就傻眼了吧,寫入的內(nèi)容是“['python',' is',' a',' cat']”。怎么才能寫成“python is a cat”呢?
文件寫操作還有一個(gè)writelines()方法,它接收的參數(shù)是由字符串組成的序列(sequence),實(shí)際寫入的效果是將全部字符串拼接在一起。字符串本身也是一種序列,所以當(dāng)參數(shù)是字符串的時(shí)候,writelines()方法等價(jià)于write()。
# 以下3種寫法等價(jià),都是寫入字符串“python is a cat” In [20]: with open('test.txt','w') as f: ...: f.writelines(['python',' is',' a',' cat']) ...: f.writelines('python is a cat') ...: f.write('python is a cat') # 以下2種寫法等價(jià),都是寫入列表的字符串版本“['python',' is',' a',' cat']” In [21]: with open('test.txt','w') as f: ...: f.write(str(['python',' is',' a',' cat'])) ...: f.writelines(str(['python',' is',' a',' cat'])) # 作為反例,以下寫法都是錯(cuò)誤的: In [22]: with open('test.txt','w') as f: ...: f.writelines([2018,'is','a','cat']) # 含非字符串 ...: f.write(['python','is','a','cat']) # 非字符串
由上可知,當(dāng)多段分散的字符串存在于列表中的時(shí)候,要用writelines()方法,如果字符串是一整段,那直接使用write()方法。如果要以整個(gè)列表的形式寫入文件,就使用str()方法做下轉(zhuǎn)化。
這個(gè)問題還沒結(jié)束,如果列表中就是有元素不是字符串,而且要把全部元素取出來,怎么辦呢?
那就不能直接使用write()和writelines()了,需要先用for循環(huán),把每個(gè)元素取出來,逐一str()處理。
In [37]: content=[1,' is',' everything'] In [38]: with open('test.txt','w') as f: ...: for i in content: ...: f.write(str(i))
需要注意的是,writelines()不會(huì)自動(dòng)換行。如果要實(shí)現(xiàn)列表元素間的換行,一個(gè)辦法是在每個(gè)元素后面加上換行符“\n”,如果不想改變元素,最好是用for循環(huán),在寫入的時(shí)候加在末尾:for i in content: f.writelines(str(i)+“\n”).
引申一下,經(jīng)過實(shí)驗(yàn),數(shù)字及元祖類型也可以作為write()的參數(shù),不需轉(zhuǎn)化。但是dict字典類型不可以,需要先用str()處理一下。字典類型比較特殊,最好是用json.dump()方法寫到文件,具體操作方法以及注意事項(xiàng),請看喵喵之前發(fā)的《假期玩得開心也不忘充電,學(xué)習(xí)Python操作JSON,網(wǎng)絡(luò)數(shù)據(jù)交換不用愁》.
總結(jié)一下,write()接收字符串參數(shù),適用于一次性將全部內(nèi)容寫入文件;writelines()接收參數(shù)是由字符串組成的序列,適用于將列表內(nèi)容逐行寫入文件。str()返回Python對象的字符串版本,使用需注意。
如何從文件中讀取內(nèi)容?
從文件中讀取內(nèi)容有如下方法:
file.read([size])
從文件讀取指定的字節(jié)數(shù),如果未給定或?yàn)樨?fù)則讀取所有。file.readline([size])
讀取整行,包括 "\n" 字符。file.readlines([sizeint])
讀取所有行并返回列表,若給定sizeint>0,則是設(shè)置一次讀多少字節(jié),這是為了減輕讀取壓力。
簡而言之,在不傳參數(shù)的情況下,read()對應(yīng)write(),讀取全部內(nèi)容;readlines()對應(yīng)writelines(),讀取全部內(nèi)容(含換行符)并以列表形式返回,每個(gè)換行的內(nèi)容作為列表的一個(gè)元素。
In [47]: with open('test.txt','r') as f:
...: print(f.read())
1 is everything.
python is a cat.
this is the end.In [48]: with open('test.txt','r') as f:
...: print(f.readlines())
['1 is everything.\n', 'python is a cat.\n', 'this is the end.']
但是,以上兩個(gè)方法有個(gè)缺點(diǎn),當(dāng)文件過大的時(shí)候,一次性讀取太多內(nèi)容,會(huì)對內(nèi)存造成極大壓力。讀操作還有一個(gè)readline()方法,可以逐行讀取。
In [49]: with open('test.txt','r') as f:
...: print(f.readline())
1 is everything.
readline()讀取第一行就返回,再次調(diào)用f.readline(),會(huì)讀取下一行。
喵喵,是否感覺跟《超強(qiáng)匯總:學(xué)習(xí)Python列表,只需這篇文章就夠了》學(xué)習(xí)過的生成器很像,需要不停調(diào)用next()獲取下一行。
這么看來,readline()太笨拙了。那么,有什么辦法可以優(yōu)雅地讀取文件內(nèi)容呢?
回過頭來看readlines()方法,它返回的是一個(gè)列表。這不奇怪么,好端端的內(nèi)容為啥要返回成列表呢?
再想想writelines()方法,把字符串列表寫入文件正是這家伙干的事,readlines()方法恰恰是它的逆操作!而writelines()方法要配合for循環(huán),所以我們把readlines()與for循環(huán)結(jié)合,看看會(huì)怎樣。
In [61]: with open('test.txt','r') as f: ...: for line in f.readlines(): ...: print(line) 1 is everything. python is a cat. this is the end. # 讀取內(nèi)容包含換行符,所以要strip()去掉換行符 In [62]: with open('test.txt','r') as f: ...: for line in f.readlines(): ...: print(line.strip()) 1 is everything. python is a cat. this is the end.
總結(jié)一下,readline()比較雞肋,不咋用;read()適合讀取內(nèi)容較少的情況,或者是需要一次性處理全部內(nèi)容的情況;而readlines()用的較多,比較靈活,因?yàn)閒or循環(huán)是一種迭代器,每次加載部分內(nèi)容,既減少內(nèi)存壓力,又方便逐行對數(shù)據(jù)處理。
多樣需求的讀寫任務(wù)
前兩部分講了文件讀寫的幾大核心方法,它們能夠起作用的前提就是,需要先打開一個(gè)文件對象,因?yàn)橹挥性谖募僮鞣幕A(chǔ)上才可以進(jìn)行讀或者寫的操作。
打開文件用的是open()方法,所以我們再繼續(xù)講講這個(gè)方法。open() 方法用于打開一個(gè)文件,并返回文件對象,在對文件進(jìn)行處理過程都需要使用到這個(gè)函數(shù),如果該文件無法被打開,會(huì)拋出 OSError。
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
open()方法的參數(shù)里file(文件)是必需的,其它參數(shù)最常用的是mode(模式)和encoding(編碼)。
先說說encoding,一般來說,打開文件的編碼方式以操作系統(tǒng)的默認(rèn)編碼為準(zhǔn),中文可能會(huì)出現(xiàn)亂碼,需要加encoding='utf-8'。
In [63]: with open('test.txt','r') as f: ...: for line in f.readlines(): ...: print(line.strip()) ----------------------- UnicodeDecodeError Traceback (most recent call last) <ipython-input-63-731a4f9cf707> in <module>() 1 with open('test.txt','r') as f: ----> 2 for line in f.readlines(): 3 print(line.strip()) UnicodeDecodeError: 'gbk' codec can't decode byte 0xa4 in position 26: illegal multibyte sequence In [65]: with open('test.txt','r',encoding='utf-8') as f: ...: for line in f.readlines(): ...: print(line.strip()) 愛貓貓 python is a cat.
再說mode,它指定文件打開的模式。
'r': 以只讀模式打開(缺省模式,必須保證文件存在)
'w':以只寫模式打開。若文件存在,則清空文件,然后重新創(chuàng)建;若不存在,則新建
'a':以追加模式打開。若文件存在,則會(huì)追加到文件的末尾;若文件不存在,則新建
常見的mode組合
'r'或'rt': 默認(rèn)模式,文本讀模式
'w'或'wt':以文本寫模式打開(打開前文件被清空)
'rb': 以二進(jìn)制讀模式打開
'ab': 以二進(jìn)制追加模式打開
'wb': 以二進(jìn)制寫模式打開(打開前文件被清空)
'r+': 以文本讀寫模式打開,默認(rèn)寫的指針開始指在文件開頭, 因此會(huì)覆寫文件
'w+': 以文本讀寫模式打開(打開前文件被清空)
'a+': 以文本讀寫模式打開(只能寫在文件末尾)
'rb+': 以二進(jìn)制讀寫模式打開
'wb+': 以二進(jìn)制讀寫模式打開(打開前被清空)
'ab+': 以二進(jìn)制讀寫模式打開
喵喵,初看起來,模式很多,但是,它們只是相互組合罷了。建議記住最基本的w、r、a,遇到特殊場景,再翻看一下就好了。
從with語句到上下文管理器
基礎(chǔ)部分講完了,下面是進(jìn)階部分。知其然,更要知其所以然。
1、with語句是初學(xué)者必會(huì)常識
首先,要解釋一下為啥前文直接就用了with語句。with語句是讀寫文件時(shí)的優(yōu)雅寫法,這已經(jīng)默認(rèn)是Python初學(xué)者必會(huì)的常識了。如果你還不會(huì),先看看用和不用with語句的對比:
# 不用with語句的正確寫法 try: f = open('test.txt','w') f.writelines(['python',' is',' a',' cat']) finally: if f: f.close() # 使用with語句的正確寫法 with open('test.txt','w') as f: f.writelines(['python',' is',' a',' cat'])
因?yàn)槲募ο髸?huì)占用操作系統(tǒng)的資源,并且操作系統(tǒng)同一時(shí)間能打開的文件數(shù)量是有限的,所以open()方法之后一定要調(diào)用close()方法。另外,讀寫操作可能出現(xiàn)IO異常的情況,所以要加try…finally,保證無論如何,都會(huì)調(diào)用到close()方法。
這樣寫萬無一失,但是實(shí)在繁瑣,一不小心還可能漏寫或者寫錯(cuò)。而with語句會(huì)保證調(diào)用close(),只需一行代碼,簡直不要太優(yōu)雅!所以,with語句是Python初學(xué)者必會(huì)技能。
2、什么是上下文管理器?
下面,重頭戲來了,什么是上下文管理器(context manager)?
上下文管理器是這樣一個(gè)對象:它定義程序運(yùn)行時(shí)需要建立的上下文,處理程序的進(jìn)入和退出,實(shí)現(xiàn)了上下文管理協(xié)議,即在對象中定義了 __enter__() 和 __exit__() 方法。
__enter__():進(jìn)入運(yùn)行時(shí)的上下文,返回運(yùn)行時(shí)上下文相關(guān)的對象,with 語句中會(huì)將這個(gè)返回值綁定到目標(biāo)對象。
__exit__(exception_type, exception_value, traceback):退出運(yùn)行時(shí)的上下文,定義在塊執(zhí)行(或終止)之后上下文管理器應(yīng)該做什么。它可以處理異常、清理現(xiàn)場或者處理 with 塊中語句執(zhí)行完成之后需要處理的動(dòng)作。
注意enter和exit的前后有兩個(gè)下劃線,Python中自帶了很多類似的方法,它們是很神秘又很強(qiáng)大的存在,江湖人常常稱其為“黑魔法”。例如,迭代器協(xié)議就實(shí)現(xiàn)了__iter__方法。
在Python的內(nèi)置類型中,很多類型都是支持上下文管理協(xié)議的,例如file,thread.LockType,threading.Lock等等。
上下文管理器無法獨(dú)立使用,它們要與with相結(jié)合,with語句可以在代碼塊運(yùn)行前進(jìn)入一個(gè)運(yùn)行時(shí)上下文(執(zhí)行_enter_方法),并在代碼塊結(jié)束后退出該上下文(執(zhí)行__exit__方法)。
with 語句適用于對資源進(jìn)行訪問的場合,確保不管使用過程中是否發(fā)生異常都會(huì)執(zhí)行必要的“清理”操作,釋放資源,比如文件使用后自動(dòng)關(guān)閉、線程中鎖的自動(dòng)獲取和釋放等。
3、自定義上下文管理器
除了Python的內(nèi)置類型,任何人都可以定義自己的上下文管理器。下面是一個(gè)示例:
class OpenFile(object): def __init__(self,filename,mode): self.filename=filename self.mode=mode def __enter__(self): self.f=open(self.filename,self.mode) self.f.write("enter now\n") return self.f #作為as說明符指定的變量的值 def __exit__(self,type,value,tb): self.f.write("exit now") self.f.close() return False #異常會(huì)被傳遞出上下文 with OpenFile('test.txt','w') as f: f.write('Hello World!\n')
最終寫入文件的結(jié)果是:
enter now
Hello World!
exit now
上下文管理器必須同時(shí)提供 __enter__() 和 _exit_() 方法的定義,缺少任何一個(gè)都會(huì)導(dǎo)致 AttributeError。
上下文管理器在執(zhí)行過程中可能會(huì)出現(xiàn)異常,_exit_() 的返回值會(huì)決定異常的處理方式:返回值等于 False,那么這個(gè)異常將被重新拋出到上層;返回值等于 True,那么這個(gè)異常就被忽略,繼續(xù)執(zhí)行后面的代碼。__exit()__ 有三個(gè)參數(shù)(exception_type, exception_value, traceback),即是異常的相關(guān)信息。
4、contextlib實(shí)現(xiàn)上下文管理器
上例中,自定義上下文管理器的寫法還是挺繁瑣的,而且只能用于類級別。為了更好地輔助上下文管理,Python 內(nèi)置提供了 contextlib 模塊,進(jìn)而可以很方便地實(shí)現(xiàn)函數(shù)級別的上下文管理器。
該模塊本質(zhì)上是通過裝飾器(decorators)和生成器(generators)來實(shí)現(xiàn)上下文管理器,可以直接作用于函數(shù)/對象,而不用去關(guān)心 __enter__() 和 __exit()__ 方法的具體實(shí)現(xiàn)。
先把上面的例子改造一下,然后我們再對照著解釋:
from contextlib import contextmanager @contextmanager def open_file(name): ff = open(name, 'w') ff.write("enter now\n") try: yield ff except RuntimeError: pass ff.write("exit now") ff.close() with open_file('test.txt') as f: f.write('Hello World!\n')
contextmanager是要使用的裝飾器,yield關(guān)鍵字將普通的函數(shù)變成了生成器。yield的返回值(ff)等于上例__enter__()的返回值,也就是as語句的值(f),而yield前后的內(nèi)容,分別是_enter_() 和 _exit_() 方法里的內(nèi)容。
使用contextlib,可以避免類定義、_enter_() 和 __exit()__方法,但是需要我們捕捉可能的異常(例如,yield只能返回一個(gè)值,否則會(huì)導(dǎo)致異常 RuntimeError),所以try…except語句不能忽略。
喵喵喵,今天的分享就到這啦??垂賯?,覺得有用的話,分享給其他同樣好學(xué)的胖友們吧~~~~
相關(guān)文章
Python GUI編程學(xué)習(xí)筆記之tkinter事件綁定操作詳解
這篇文章主要介紹了Python GUI編程學(xué)習(xí)筆記之tkinter事件綁定操作,結(jié)合實(shí)例形式分析了Python GUI編程tkinter事件綁定常見操作技巧與使用注意事項(xiàng),需要的朋友可以參考下2020-03-03Python語言實(shí)現(xiàn)獲取主機(jī)名根據(jù)端口殺死進(jìn)程
這篇文章主要介紹了Python語言實(shí)現(xiàn)獲取主機(jī)名根據(jù)端口殺死進(jìn)程的相關(guān)資料,需要的朋友可以參考下2016-03-03如何通過雪花算法用Python實(shí)現(xiàn)一個(gè)簡單的發(fā)號器
這篇文章主要介紹了如何通過雪花算法用Python實(shí)現(xiàn)一個(gè)簡單的發(fā)號器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07