Python文件讀寫指南

做者:豌豆花下貓,某985高校畢業生, 兼具極客思惟與人文情懷 。公衆號【Python貓】, 專一python技術、數據科學和深度學習,力圖創造一個有趣又有用的學習分享平臺。html

對於初學者來講,一份詳盡又清晰明白的指南很重要。今天,貓貓跟你們一塊兒,好好學習Python文件讀寫的內容,這部份內容特別經常使用,掌握後對工做和實戰都大有益處。學習是按部就班的過程,欲速則不達。文章較長,建議你們收藏,以備複習查閱哦。
python

一、如何將列表數據寫入文件?
二、如何從文件中讀取內容?
三、多樣需求的讀寫任務
四、從with語句到上下文管理器
sql

如何將列表數據寫入文件?

首先,咱們來看看下面這段代碼,並思考:這段代碼有沒有問題,若是有問題的話,要怎麼改?json

li = ['python',' is',' a',' cat']
with open('test.txt','w'as f:
    f.write(li)

如今公佈答案,這段代碼會報錯:ruby

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 strnot list

以上代碼的想法是將list列表內容寫入txt文件中,可是報錯 TypeError: write() argument must be str。就是說,write()方法必須接受字符串(str)類型的參數。微信

Python中內置了str()方法,能夠返回字符串版本的對象(Return a string version of object)。因此,上面的例子中,咱們試試把 f.write(li) 改成 f.write(str(li)) ,先作一下字符串類型的轉化看看。代碼略。app

此次沒有報錯了,可是打開文件就傻眼了吧,寫入的內容是「['python',' is',' a',' cat']」。怎麼才能寫成「python is a cat」呢?函數

文件寫操做還有一個writelines()方法,它接收的參數是由字符串組成的序列(sequence),實際寫入的效果是將所有字符串拼接在一塊兒。字符串自己也是一種序列,因此當參數是字符串的時候,writelines()方法等價於write()。學習

# 如下3種寫法等價,都是寫入字符串「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種寫法等價,都是寫入列表的字符串版本「['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']))

# 做爲反例,如下寫法都是錯誤的:
In [22]:  with open('test.txt','w'as f:
    ...:      f.writelines([2018,'is','a','cat']) # 含非字符串
    ...:      f.write(['python','is','a','cat']) # 非字符串

由上可知,當多段分散的字符串存在於列表中的時候,要用writelines()方法,若是字符串是一整段,那直接使用write()方法。若是要以整個列表的形式寫入文件,就使用str()方法作下轉化。ui

這個問題還沒結束,若是列表中就是有元素不是字符串,並且要把所有元素取出來,怎麼辦呢?

那就不能直接使用write()和writelines()了,須要先用for循環,把每一個元素取出來,逐一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()不會自動換行。若是要實現列表元素間的換行,一個辦法是在每一個元素後面加上換行符「\n」,若是不想改變元素,最好是用for循環,在寫入的時候加在末尾:for i in content:  f.writelines(str(i)+「\n」).

引伸一下,通過實驗,數字及元祖類型也能夠做爲write()的參數,不需轉化。可是dict字典類型不能夠,須要先用str()處理一下。字典類型比較特殊,最好是用json.dump()方法寫到文件。

總結一下,write()接收字符串參數,適用於一次性將所有內容寫入文件;writelines()接收參數是由字符串組成的序列,適用於將列表內容逐行寫入文件。str()返回Python對象的字符串版本,使用需注意。

如何從文件中讀取內容?

從文件中讀取內容有以下方法:

file.read([size])
從文件讀取指定的字節數,若是未給定或爲負則讀取全部。

file.readline([size])
讀取整行,包括 "\n" 字符。

file.readlines([sizeint])
讀取全部行並返回列表,若給定sizeint>0,則是設置一次讀多少字節,這是爲了減輕讀取壓力。

簡而言之,在不傳參數的狀況下,read()對應write(),讀取所有內容;readlines()對應writelines(),讀取所有內容(含換行符)並以列表形式返回,每一個換行的內容做爲列表的一個元素。

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.']

可是,以上兩個方法有個缺點,當文件過大的時候,一次性讀取太多內容,會對內存形成極大壓力。讀操做還有一個readline()方法,能夠逐行讀取。

In [49]: with open('test.txt','r'as f:
    ...:     print(f.readline())
1 is everything.

readline()讀取第一行就返回,再次調用f.readline(),會讀取下一行。

這麼看來,readline()太笨拙了。那麼,有什麼辦法能夠優雅地讀取文件內容呢?

回過頭來看readlines()方法,它返回的是一個列表。這不奇怪麼,好端端的內容爲啥要返回成列表呢?

再想一想writelines()方法,把字符串列表寫入文件正是這傢伙乾的事,readlines()方法偏偏是它的逆操做!而writelines()方法要配合for循環,因此咱們把readlines()與for循環結合,看看會怎樣。

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.

# 讀取內容包含換行符,因此要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.

總結一下,readline()比較雞肋,不咋用;read()適合讀取內容較少的狀況,或者是須要一次性處理所有內容的狀況;而readlines()用的較多,比較靈活,由於for循環是一種迭代器,每次加載部份內容,既減小內存壓力,又方便逐行對數據處理。

多樣需求的讀寫任務

前兩部分講了文件讀寫的幾大核心方法,它們可以起做用的前提就是,須要先打開一個文件對象,由於只有在文件操做符的基礎上才能夠進行讀或者寫的操做。

打開文件用的是open()方法,因此咱們再繼續講講這個方法。open() 方法用於打開一個文件,並返回文件對象,在對文件進行處理過程都須要使用到這個函數,若是該文件沒法被打開,會拋出 OSError。

open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

open()方法的參數裏file(文件)是必需的,其它參數最經常使用的是mode(模式)和encoding(編碼)。

先說說encoding,通常來講,打開文件的編碼方式以操做系統的默認編碼爲準,中文可能會出現亂碼,須要加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':以只寫模式打開。若文件存在,則清空文件,而後從新建立;若不存在,則新建
'a':以追加模式打開。若文件存在,則會追加到文件的末尾;若文件不存在,則新建

常見的mode組合
'r'或'rt':   默認模式,文本讀模式
'w'或'wt':以文本寫模式打開(打開前文件被清空)
'rb':       以二進制讀模式打開
'ab':      以二進制追加模式打開
'wb':      以二進制寫模式打開(打開前文件被清空)
'r+':       以文本讀寫模式打開,默認寫的指針開始指在文件開頭, 所以會覆寫文件
'w+':      以文本讀寫模式打開(打開前文件被清空)
'a+':      以文本讀寫模式打開(只能寫在文件末尾)
'rb+':     以二進制讀寫模式打開
'wb+':   以二進制讀寫模式打開(打開前被清空)
'ab+':    以二進制讀寫模式打開

喵喵,初看起來,模式不少,可是,它們只是相互組合罷了。建議記住最基本的w、r、a,遇到特殊場景,再翻看一下就行了。

從with語句到上下文管理器

基礎部分講完了,下面是進階部分。知其然,更要知其因此然。

一、with語句是初學者必會常識

首先,要解釋一下爲啥前文直接就用了with語句。with語句是讀寫文件時的優雅寫法,這已經默認是Python初學者必會的常識了。若是你還不會,先看看用和不用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'])

由於文件對象會佔用操做系統的資源,而且操做系統同一時間能打開的文件數量是有限的,因此open()方法以後必定要調用close()方法。另外,讀寫操做可能出現IO異常的狀況,因此要加try…finally,保證不管如何,都會調用到close()方法。

這樣寫萬無一失,可是實在繁瑣,一不當心還可能漏寫或者寫錯。而with語句會保證調用close(),只需一行代碼,簡直不要太優雅!因此,with語句是Python初學者必會技能。

二、什麼是上下文管理器?

下面,重頭戲來了,什麼是上下文管理器(context manager)?

上下文管理器是這樣一個對象:它定義程序運行時須要創建的上下文,處理程序的進入和退出,實現了上下文管理協議,即在對象中定義了 __enter__() 和 __exit__() 方法。


__enter__():進入運行時的上下文,返回運行時上下文相關的對象,with 語句中會將這個返回值綁定到目標對象。 


__exit__(exception_type, exception_value, traceback):退出運行時的上下文,定義在塊執行(或終止)以後上下文管理器應該作什麼。它能夠處理異常、清理現場或者處理 with 塊中語句執行完成以後須要處理的動做。

注意enter和exit的先後有兩個下劃線,Python中自帶了不少相似的方法,它們是很神祕又很強大的存在,江湖人經常稱其爲「黑魔法」。例如,迭代器協議就實現了__iter__方法。

在Python的內置類型中,不少類型都是支持上下文管理協議的,例如file,thread.LockType,threading.Lock等等。

上下文管理器沒法獨立使用,它們要與with相結合,with語句能夠在代碼塊運行前進入一個運行時上下文(執行_enter_方法),並在代碼塊結束後退出該上下文(執行__exit__方法)。

with 語句適用於對資源進行訪問的場合,確保無論使用過程當中是否發生異常都會執行必要的「清理」操做,釋放資源,好比文件使用後自動關閉、線程中鎖的自動獲取和釋放等。

三、自定義上下文管理器

除了Python的內置類型,任何人均可以定義本身的上下文管理器。下面是一個示例:

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   #異常會被傳遞出上下文
with OpenFile('test.txt','w') as f:
    f.write('Hello World!\n')

最終寫入文件的結果是:

enter now
Hello World!
exit now

上下文管理器必須同時提供 __enter__() 和 _exit_() 方法的定義,缺乏任何一個都會致使 AttributeError。 

上下文管理器在執行過程當中可能會出現異常,_exit_() 的返回值會決定異常的處理方式:返回值等於 False,那麼這個異常將被從新拋出到上層;返回值等於 True,那麼這個異常就被忽略,繼續執行後面的代碼。__exit()__ 有三個參數(exception_type, exception_value, traceback),便是異常的相關信息。

四、contextlib實現上下文管理器

上例中,自定義上下文管理器的寫法仍是挺繁瑣的,並且只能用於類級別。爲了更好地輔助上下文管理,Python 內置提供了 contextlib 模塊,進而能夠很方便地實現函數級別的上下文管理器。

該模塊本質上是經過裝飾器(decorators)和生成器(generators)來實現上下文管理器,能夠直接做用於函數/對象,而不用去關心 __enter__() 和 __exit()__ 方法的具體實現。

先把上面的例子改造一下,而後咱們再對照着解釋:

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關鍵字將普通的函數變成了生成器。yield的返回值(ff)等於上例__enter__()的返回值,也就是as語句的值(f),而yield先後的內容,分別是_enter_() 和 _exit_() 方法裏的內容。

使用contextlib,能夠避免類定義、_enter_() 和 __exit()__方法,可是須要咱們捕捉可能的異常(例如,yield只能返回一個值,不然會致使異常 RuntimeError),因此try…except語句不能忽略。

以上就是本文的所有內容,但願對你們的學習有所幫助。若是以爲文章不錯,動手轉發支持一下哦!


感謝您的閱讀!想了解更多有關技巧,請關注個人微信公衆號「R語言和Python學堂」,我將按期更新相關文章。同時也歡迎你們積極投稿,促進交流。

個人專欄:

  • 簡書:https://www.jianshu.com/u/981ba7d6b4a6

  • 知乎:https://www.zhihu.com/people/zoro-3-92/activities

本文分享自微信公衆號 - R語言和Python學堂(gh_02c4f77a735e)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索