0基礎學Python之二十二:文件操作(下)

0基礎學Python之二十二:文件操作(下)

來自專欄可樂編程0基礎學Python之二十二:文件操作(下)?

v.qq.com

Hi 大家好,我是王可樂,歡迎回到我們的零基礎學 Python 課程。今天我們繼續來聊文件的話題。

上一節課中,我們已經看到了如何使用 Python 來按行讀取一個文本文件。我們再來看一下這段代碼,打開文件 read_file.py。

f = open(rC:colecode
ead_file.py, r)for l in f.readlines(): print(l, end=)f.close()

我們看到,這裡的變數 f 是一個用 open() 函數返回的文件對象。我們使用 f 的 readlines() 函數來按照行讀取整個文件,返回一個行的列表。實際上,文件對象 f 還支持更多的讀取操作,例如,和 readlines() 函數類似的還有一個叫做 readline() 的函數,它一次只讀取並返迴文件的一行。我們打開一個 Python Shell 來演示一下:

>>> f = open(rC:colecode
ead_file.py, r) # 調用 open() 函數打開文件>>> l = f.readline()>>> l"f = open(rC:colecode
ead_file.py, r)
" # 可以看到,變數 l 是一個字元串,內容是我們打開的源碼文件的第一行,並且結尾由一個換行符

readline() 函數可以反覆調用,每次調用時都會返迴文件的下一行。我們來看一下:

>>> l = f.readline() # 我們再次調用f.readline(),還是賦值給變數 l>>> lfor l in f.readlines():
# 這時 l 被賦值成了文件的第二行>>> l = f.readline() # 再來調用一次>>> l" print(l, end=)
" # 變成了第三行>>> l = f.readline()>>> lf.close()
# 這時已經讀取出來了第四行,也就是最後一行>>> l = f.readline() # 如果繼續調用 f.readline() 呢?>>> l # 我們發現,這時 l 成了一個空字元串

大家可以再試一試,文件讀取完畢之後,無論怎麼繼續調用 readline(),都只會返回空字元串了。

我們看到,每次調用 readline() 時,它都知道上次我們讀到了文件的哪個位置,然後給我們返回下一行。這是因為,文件對象 f 中保存了一個叫做文件指針的東西。

在計算機系統中,文件往往被抽象成數據流的形狀。對於文本文件,我們可以把它理解成一個字元組成的數據流,一個文本文件就可以看作是一串從前往後排列的字元。而文件指針的作用就是指向文件的數據流上某一個位置,作為當前讀操作的開始位置。我們可以使用文件對象的 tell() 函數來查看當前文件指針的位置。

>>> f.close() # 我們養成良好的習慣,先關閉文件 f>>> f = open(rC:colecode
ead_file.py, r) # 然後我們重新打開它>>> f.tell()0 # 返回 0,對於剛剛打開的文件,它的文件指針就指向 0 位置>>> l = f.readline() # 輸入 f.readline() 讀取一行,保存到變數 l>>> l"f = open(rC:colecode
ead_file.py, r)
" # 輸出第一行>>> f.tell() # 然後我們再來看一下文件指針的位置44 # 可以看到,這時文件指針已經指向了位置 44>>> len(l) # 我們來看一下剛剛讀取的文件第一行的長度43 # 返回43

可以看到,這裡的文件指針採用的是以字元為單位的位置。我們調用 readline() 讀取了長度為 43 的一行之後,文件指針就從 0 變成了 44,指向了第 45 個字元,也就是第二行的第一個字元。為什麼 l 中保存的字元串的長度是43,而指針卻指向第45個字元呢?原因其實是,在windows 中,一行文本是以兩個特殊字元結尾的「
」,即,遇到這兩個字元則意味著文本換行。但是在Linux和Mac下,一行文本是以「
」結尾的。這兩一來,如果一個文本文件被跨系統使用,在處理上會有一些小問題。Python 3的文件處理,會自動按照Linux和Mac下的方式來做,所以l中的這行文本,在讀入時,
這個特殊符號被自動去掉了。所以事實上這個文本的第一行其實應該有44個字元,所以文件指針才指向了第45個字元。

我們也可以人為修改文件指針的位置,Python 提供了 seek() 函數來實現這個功能。例如,剛剛讀取了一行之後,文件指針指向了 44(第45個字元):

>>> f.seek(2) # 我們輸入 f.seek(2),將文件指針改為 2>>> f.readline() # 然後我們再來試一試讀取一行"= open(rC:\colecode\read_file.py, r)
"

可以看到,這時我們又讀取了一遍第一行,只是少了前兩個字元。因為 seek(2) 把文件指針改為了 2,也就指向了第三個字元,就是等號。

看到這裡,可樂請大家暫停視頻想一想,如果換用 readline() 函數來代替 readlines() 函數,該如何改寫上一節課讀取文件的例子呢?

好了,可樂假設大家已經思考過這個問題了,我們來看下面這段代碼:

with open(rC:colecode
ead_file.py, r) as f: while True: l = f.readline() if l == : break print(l, end=)

這裡,可樂換用了一個 while 循環來反覆調用 readline() 函數。當 readline() 函數返回空字元串時,我們使用 break 跳出循環,結束程序,否則我們列印讀到的行。

和 readlines() 函數類似,文件對象還支持 read() 這個函數。它也是讀取全部文件內容,但是 readlines() 將整個文件按行切分成一個字元串的列表,而 read() 則只返回一個單獨的字元串,包含文件的全部內容。此外,readlines() 函數和 read() 函數在執行一次之後,都會把文件指針指向文件最後,因此如果不使用 seek() 修改文件指針,那麼重複執行這兩個函數就只能得到一個空列表或者空字元串了。最後,以上這些 read 系列的函數都支持一個可選的參數,可以指定每次讀取的最大字元個數。這些內容可樂就不為大家一一演示了,希望大家在自己的 Python Shell 裡面試一下,多多實踐,多多查閱文檔。

可以讀取文件,Python 自然也可以寫入文件。我們照例先來看一個例子:

with open(rC:colecodewrite_test.txt, w) as f: num_chars = f.write(Hello world!
) print(Wrote {} chars.format(num_chars))

和讀取文件類似,寫入文件之前,也需要使用 open() 函數來創建一個文件對象,因此也支持使用 with 上下文管理器的寫法。

不同的地方是,讀取文件時我們給 open() 函數傳遞的第二個參數是 r,也就是以只讀模式打開,以只讀模式打開的文件只能被讀取;與之相對的是,這裡我們為 open() 函數傳遞了參數 w,就是以所謂"只寫"的模式打開文件,這時我們的文件對象 f 上就只能做寫入操作。如果在只寫模式打開的文件對象上試圖調用 read() 等函數,那麼 Python 就會報錯;反過來如果在只讀模式打開的文件對象上試圖調用 write() 函數,也會報錯。

除了 r, w 這兩種模式之外,Python 的 open() 函數還支持 r+, a 模式。其中,以 r+ 模式打開的文件對象即可以進行讀操作,也可以進行寫操作;而以 a 模式,也就是 append 模式打開的文件對象也是一種只寫模式。append 只寫模式與 w 只寫模式的區別在於,w 模式打開文件時,文件指針默認為 0,如果文件已經有內容,則新寫入的內容會覆蓋原有的內容;而 append 模式打開的文件對象,其文件指針位於已有內容的末尾,新寫入的內容會寫到文件後部,特別適合向文件內部追加內容。

此外,打開模式 r, w, r+, a 後面都支持一個 b 標記,也就是模式 rb, wb, r+b 或者 ab。我們前面的代碼里,默認 Python 是以文本模式打開的文件,文本模式適合文本文件的讀寫,例如在不同的操作系統平台上,Python 會自動識別行尾應該用
還是
。而 b 標記位表示以二進位模式打開文件,這種模式適合所有非文本文件的讀寫。實踐中直接進行二進位文件讀寫的情況比較少,因此在我們的初級課程中可樂就不涉及這些內容了。

好了,講了這麼多打開模式,我們回來看看打開文件之後的操作。這裡,我們只是用 write() 函數向文件中寫入了一行字元 Hello world!,並且在最後添加了一個換行符
。write() 函數會返回寫入文件的字元數,我們把它保存在變數 num_chars 裡面,最後列印出來。

除了 write() 函數,Python 中還支持 writelines() 函數。這個函數和前面的 readlines() 函數剛好相反,它把一個列表一行一行地寫入文件中去。這次我們來使用 Python Shell 演示一下:

>>> line_list = [line 1
, line 2
, line 3
] # 定義一個字元串的列表,注意這裡的每個字元串後面都帶有換行符
,因為 writelines() 函數寫文件時並不會自動為每行添加換行符>>> f = open(rC:colecodewrite_test.txt, w) # 然後,我們打開一個文件,模式為只寫模式>>> f.writelines(line_list) # 最後,我們把列表寫入文件

然後,請大家打開自己的 C 盤 colecode 文件夾,看看裡面是不是已經多了一個 write_test.txt 文件了?讓我們打開來看一下。什麼?文件里竟然什麼內容都沒有。我們剛剛寫入的三行字元串哪裡去了呢?

這裡,可樂需要給大家解釋一個新的概念,叫做帶緩衝的讀寫。我們知道,一般我們都把文件保存在磁碟等慢速讀寫設備上,這裡的慢速是指相對於內存而言,硬碟等設備的讀寫速度實在是太慢了。由於內存的讀寫速度要快很多,因此程序把要寫入文件的內容先寫入內存,然後再由操作系統慢慢寫入磁碟,對於程序而言效率就會高很多。這裡我們用於暫存讀寫數據的內存區域,就叫做緩衝區,也叫緩存。除了上面的原因,硬碟這種設備每次讀寫一小塊一小塊的數據效率較低,而一次性讀寫一大塊的文件則效率較高,所以在內存中先把數據攢起來,最後一批一批寫入也是比較划算的。

Python 在寫文件時默認就使用帶內存緩衝的寫入模式。因此我們剛剛調用 writelines() 函數之後,列表數據只是被寫入了緩衝區。Python 會在緩衝區要滿了,或者文件對象關閉時再將這些內容寫入文件。如果我們想立刻將緩衝區內的內容寫入文件,那麼可以使用 flush() 函數,主動將緩衝區數據刷入磁碟,這樣就能夠立刻看到寫入效果了。此外,調用 close() 函數關閉文件也可以觸發 flush 操作。我們接著剛才的 Shell 示例演示一下:

>>> f.flush() # 調用 f.flush() 函數>>> f.close() # 然後我們養成好習慣,關閉這個文件,實際上直接關閉文件也會起到 flush() 的作用,這裡可樂都為大家演示一下

OK,我們重新打開 write_test.txt 這個文件,看看是不是已經寫入了三行數據了呢?

好了,文件讀寫的內容就先介紹這麼多,Python 的文件對象還支持很多其他的操作函數。可樂希望大家不僅能跟著可樂的課程來練習,也能查閱文檔熟悉更多我們沒能講到的內容,這樣才能更快地進步哦。

這節課的最後,可樂再給大家介紹一個關於文件路徑的操作。在前面的例子中,我們忽略了一個問題,就是文件和目錄存在不存在的問題。我們來看個例子:

>>> f = open(rC:colecode
ot_exist.txt, r) # 首先我們嘗試打開一個不存在的文件Traceback (most recent call last): File "<stdin>", line 1, in <module>FileNotFoundError: [Errno 2] No such file or directory: C:colecode
ot_exist.txt>>> # 我們看到,Python 報了一個錯誤說文件不存在>>> f = open(rC:colecode
ot_exist.txt, w) # 而如果我們用 w 模式打開不存在的文件,我們看到這次沒有報錯,這時 Python 會自動為我們創建這個文件>>> # 不過,如果文件前面的目錄,也就是文件夾也不存在呢?>>> f = open(rC:colecodeend_of_the_universedinner.txt, w) #這裡 end_of_the_universe 是一個不存在的目錄Traceback (most recent call last): File "<stdin>", line 1, in <module>FileNotFoundError: [Errno 2] No such file or directory: C:colecodeend_of_the_universedinner.txt>>> # 這一次,Python 又報錯了呢,看來可樂在宇宙盡頭吃晚飯的願望不幸落空了

從這些例子里我們可以看到,open() 函數可以比較好地處理文件不存在的問題,但是不能處理路徑中目錄不存在的問題。這裡,可樂為大家簡單介紹下 Python 的另一個內置模塊 os,我們還是先來演示一下:

>>> import os # 首先,我們還是引入這個模塊>>> os.path.exists(rC:colecodeend_of_the_universe\) # 然後,我們來檢驗一下 end of the universe 這個目錄是否存在False # 返回 False

注意,在windows中,由於目錄分割使用反斜杠「」,所以在表示目錄時很容易出現轉義的問題,比如在這裡表示目錄時,雖然已經在目錄名前使用「r」,結尾必須用兩個反斜杠"",否則python shell會認為字元串沒有結束。下面的例子中也是一樣。

這裡,我們使用了 os 模塊的子模塊 path 提供的函數 exists,來檢驗目錄是否存在。它返回了 False,說明這個目錄目前不存在。那麼,我們來創建一下這個目錄,也就是大家熟悉的新建一個文件夾:

>>> os.makedirs(rC:colecodeend_of_the_universe
estaurant\)

這裡,我們使用 os 模塊提供的 makedirs() 函數,在 colecode 目錄下創建了 end of the universe 目錄,並且在裡面又創建了 restaurant 這個子目錄。我們打開的 C 盤 colecode 這個目錄看一下,可以看到,我們已經成功創建了剛才不存在的目錄了。

因此,在不確定目錄是否存在時,我們就可以先使用 path 的 exists() 函數來檢查一下。如果發現目錄不存在,那麼就用 makedirs() 函數來創建,之後再使用 open() 函數在內部創建文件。不過,makedirs() 函數還有一個更好的參數,我們可以來看一下:

>>> os.makedirs(rC:colecodeend_of_the_universe
estaurant\) # 首先,我們把剛剛輸入過的命令再次輸入一遍Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/Us...ython3.6/os.py", line 220, in makedirs mkdir(name, mode)FileExistsError: [Errno 17] File exists: C:cole...urant

可以看到,在全部目錄都已經存在的情況下,調用 makedirs() 就會報一個錯,說目錄已經存在,不能再創建了。我們可以使用 makedirs() 的 exist_ok 參數來調整這個行為:

>>> os.makedirs(rC:colecodeend_of_the_universe
estaurant\, exist_ok=True) # 輸入,念代碼,傳入 exist_ok 參數,賦值為 True

可以看到,這此 makedirs() 函數就沒有再報錯了。因此,如果我們在寫文件之前不確定目錄是否存在,就可以只使用 makedirs() 函數來嘗試創建目錄。如果目錄存在,那麼也不會有什麼問題;如果目錄不存在,我們也會創建它。

除此之外,os 模塊還提供了很多操作系統相關的工具函數,特別是 os.path 子模塊里,包含了很多於目錄相關的操作功能。我們在這裡只是演示了 os 模塊的一個小小的功能,更多高級的功能可樂會放在以後的課程中再為大家講解。

好了,今天的內容有點長,我們就先聊到這裡。學完了這些內容,你就可以輕鬆地使用 Python 讀取、寫入文本文件了。希望大家能夠多多練習,可樂也鼓勵大家多多查閱文檔,這樣才能快速進步哦。

在下一節課里,可樂會繼續文件和數據的話題,為大家介紹一種編程時非常有用的數據表示格式,JSON,敬請期待哦。如果你喜歡我們的課程,歡迎關注我們的公眾號「可樂編程」,同時也請轉發給你的朋友們哦。可樂感謝大家的支持,我們下次課再見!


推薦閱讀:

TAG:Python | Python入門 | 編程入門 |