一、概述
作為IO操作的重要部分,文件操作需要經常用到,下面簡述下python中操作文件的流程:
1. 打開文件,得到一個文件句柄並賦值給一個變量
f = open("test.txt","r",encoding="utf-8") #open創建句柄並打開文件,由於是對文件進行操作,因此這里的變量習慣性地命名為f,但命名為其他變量顯然也OK
2. 通過句柄對文件進行操作
content = f.read()
3. 關閉文件
f.close()
注意:
1. 盡管在這里我們說通過python操作(讀寫)文件,但實際的過程是python不能直接度讀寫文件,只能通過向操作系統提供的相應接口發出請求,要求打開一個文件對象,然后由操作系統的接口來完成對文件的具體操作。這里的文件句柄即文件描述符,唯一標識了一個文件對象。
2. 完整的文件操作一定包含了最后一步關閉處理,否則會造成系統資源的嚴重浪費,每個程序員都應明確踐行這點。
3. 對文件進行讀寫操作時,系統維護了一個指針,指向當前處理完畢后所處文件的位置。
可通過f.seek(n) 將文件指針移動到n處,n為0則表示移動到文件開頭位置
可通過f.tell()來獲取當前文件指針的位置
二、文件的打開模式
文件的操作需遵循以下規范:
句柄變量 = open(文件路徑,打開模式,文件編碼)
其中文件路徑最好定義絕對路徑,除非相對路徑非常確定,打開模式默認的是r模式,文件編碼默認為utf-8。
普通打開模式
- r模式
只讀模式,不可對文件進行寫處理。
f = open("test.txt", "r") print(f.read()) f.write("Hello\n") f.close()
示例程序運行結果:
從上圖可看出以r模式打開后文件不可寫。
- w模式
寫模式,如果文件不存在則先創建空文件然后寫入指定內容,否則則直接覆蓋寫原文件。注意該模式下不可對文件進行讀處理。
f = open("test.txt", "w") f.write("Hello\n") print(f.read()) f.close()
與r模式下相同的測試文件進行寫處理后的運行輸出:
此時測試文件內容僅僅保留了write進去的內容,因此是覆蓋寫處理:
但是,需要注意的是這里所說的覆蓋寫處理僅僅適用於打開文件對象后第一次寫入,如果打開文件在close之前繼續寫入,則進行追加寫處理(隨着新內容的不斷寫入,文件指針在同步移動)。
驗證一下吧:
f = open("test.txt", "r", encoding="utf-8") print("The content of source file---before write:") print(f.read()) print("Begin to write for the first time") f = open("test.txt", "w") f.write("11\n") f.tell() print("Begin to write for the second time") f.write("22\n") f.close() f = open("test.txt", "r", encoding="utf-8") print("After write:") print(f.read()) f.close()
輸出結果:
從上圖可看出open文件之后第一次寫是覆蓋寫。close前的第二次寫卻是追加寫。
- a模式
追加模式,寫入的內容總是在當前文件的末尾追加進去,無論怎么移動指針。注意該模式下仍然不可對文件進行讀處理。
續上文,追加寫操作之前測試文件內容僅有hello一行。
f = open("test.txt", "a") f.write("yesterday\n") print(f.tell()) f.seek(0) print(f.tell()) f.write("write for the second time\n") print(f.read()) f.close()
追加寫處理后的輸出:
盡管在這里我們通過seek(下文會詳述)將指針重置到文件頭位置,但是新增的文件內容依然是在文件末尾追加的,並且不可讀。
同時讀寫模式
- r+模式
讀寫模式,同時具備讀和寫權限,但寫入時默認是把內容寫入到文件末尾進行追加寫,而不是覆蓋寫,除非在寫入時先把指針移動到文件頭。
f = open("test.txt", "r+") print("Before write:") print(f.read()) #獲取源文件內容 f.write("yesterday\n") #執行默認的寫處理 print(f.tell()) f.seek(0) #移動文件指針到文件頭以便讀取文件的全部內容 print("After write for the first time:") print(f.read()) f.seek(0) #移動文件指針到文件頭后再次寫入 f.write("yesterday again\n") #進行第二次寫入測試 print("After write for the second time:") f.seek(0) print(f.read()) #讀取第二次寫操作后的全部文件內容 f.close()
程序運行輸出:
上述運行結果充分論證了r+模式下的寫處理過程。
- w+模式
寫讀模式,同時具備寫和讀權限,先創建新的空文件,然后寫入內容。該模式實際不常用。
- a+模式
追加內容的同時可讀,注意新內容一定是在源文件末尾追加,同時在讀取文件內容時文件指針默認就在文件末尾,因此不移動文件指針到文件頭部是不能讀取到文件內容的。
二進制打開模式
二進制文件要以二進制模式進行讀取,該模式下打開文件時不能傳遞編碼參數。
常見的二進制格式文件有音頻、視頻、網絡傳輸的文件(ftp二進制傳輸模式),因此處理這些文件上時需要用到二進制打開模式。
常見的二進制打開模式下的操作有rb(讀二進制),wb(寫二進制)和ab(追加二進制)。
需要注意的是:
wb寫入時一定要在write后面調用encode()方法將字符串轉換為二進制(字節碼)寫入,同理rb時如果要輸出字符串,則需要在read后面調用decode()將二進制字節碼轉換為字符串輸出,原因是python3中對字節碼和字符串進行了嚴格的區分。
三、文件的常用函數及基本操作
文件內容:
Somehow, it seems the love I knew was always the most destructive kind
不知為何,我經歷的愛情總是最具毀滅性的的那種
Yesterday when I was young
昨日當我年少輕狂
-
文件讀
python中提供了read(),readline()和readlines()三種讀取文件的方法。以下是三者的詳細對比:
(1)read()
讀取文件內容,並以字符串形式返回。可傳遞一個int參數進去,表示讀取文件的多少個字符。默認不帶參數表示讀取文件的全部內容,因此僅適合讀取文件size明確且較小的文件,否則等待時間將是一個嚴重的考驗。
f = open("test.txt", "r", encoding="utf-8") print(f.read()) f.seek(0) #重置文件指針 print("==========") print(f.read(20)) #讀取20個字符 print(type(f.read())) #確定返回類型 f.close() 輸出: Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 昨日當我年少輕狂 ========== Somehow, it seems th <class 'str'> #返回字符串
(2)readline()
一次讀取一行文件內容,並以字符串形式返回。相對於一次性讀取全部內容效率上有犧牲,但對於大文件和size不確定的文件,可避免系統資源占用過多(內存不夠)和耗時較長的問題。實際使用中建議嵌套在循環中對文件進行逐行讀取。
f = open("test.txt", "r", encoding="utf-8") print(f.readline()) print(type(f.readline())) print("\n===1.read in loop=====:\n") while True: line = f.readline() if line: print(line) else: break print("\n===2.read in loop again=====:\n") f.seek(0)#重置文件指針 while True: line = f.readline() if line: print(line) else: break f.close() 輸出: Somehow, it seems the love I knew was always the most destructive kind <class 'str'> ===1.read in loop=====: #未重置文件指針時讀取下一行 Yesterday when I was young 昨日當我年少輕狂 ===2.read in loop again=====:#重置文件指針后從文件頭開始讀取 Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 昨日當我年少輕狂
(3)readlines()
一次性讀取全部文件內容,並以列表形式返回。因此也僅僅適用於讀取文件size確定且較小的文件。
f = open("test.txt", "r", encoding="utf-8") print(f.readlines()) print(type(f.readline())) print(f.tell()) #獲取文件指針位置 f.close() 輸出: ['Somehow, it seems the love I knew was always the most destructive kind\n', '不知為何,我經歷的愛情總是最具毀滅性的的那種\n', 'Yesterday when I was young\n', '昨日當我年少輕狂'] <class 'list'> 192
總結:
擴展:逐行讀取文件的方法
(1)for line in f
逐行讀取文件,內存每次僅僅加載最新的一行,加載完就銷毀
f = open("test.txt", "r", encoding="utf-8") for line in f: print (line) print(type(line)) f.close() 輸出: Somehow, it seems the love I knew was always the most destructive kind <class 'str'> 不知為何,我經歷的愛情總是最具毀滅性的的那種 <class 'str'> Yesterday when I was young <class 'str'> 昨日當我年少輕狂 <class 'str'>
(2)把f.readline()嵌套到循環中
逐行讀取文件,每次僅加載一行,代碼相當於for line in f略顯冗余f = open("test.txt", "r") while True: line = f.readline() if line: print(line) print(f.tell()) #驗證下文件指針位置 else: break f.close() 輸出: Somehow, it seems the love I knew was always the most destructive kind 72 不知為何,我經歷的愛情總是最具毀滅性的的那種 116 Yesterday when I was young 143 昨日當我年少輕狂 159
從文件指針可看出每次移動一行。
(3)把f.readlines()嵌套到循環中
一次性讀取文件的全部內容,然后逐行打印,不適合大文件使用。f = open("test.txt", "r") for line in f.readlines(): print(line) print(f.tell()) #還是獲取下文件指針位置 f.close() 輸出: Somehow, it seems the love I knew was always the most destructive kind 159 #文件指針一下跳到文件末尾 不知為何,我經歷的愛情總是最具毀滅性的的那種 159 Yesterday when I was young 159 昨日當我年少輕狂 159
- tell()
f.tell()返回輸出當前文件指針所處位置。前文很多地方已經展示了它強大的一面。 - seek()
f.seek()可將文件指針重置到某個特定位置,使用時需要傳入int參數,0表示文件頭。目前暫時只會使用這點。 - encoding()
返回文件的編碼格式。
f = open("test.txt", "r", encoding="utf-8") print(f.encoding) f.close() 輸出: utf-8
- fileno()
返回文件描述符,目前還不清楚實際的應用場景。 - seekable
文件指針是否可操作移動,通過print可獲取到布爾類型的返回值,可移動則返回true。 - readable
文件是否可讀,通過print可獲取到布爾類型的返回值。
f = open("test.txt", "w", encoding="utf-8") print(f.readable()) f.close() 輸出: False
- writable
判斷文件是否可寫,通過print可獲取到布爾類型的返回值。
f = open("test.txt", "w", encoding="utf-8") print(f.writable()) f.close() 輸出: True
- flush()
將緩存的數據同步刷寫到磁盤。
打印類似於Linux下yum安裝軟件包或python下安裝軟件包的進度條:
import sys import time for i in range(30): sys.stdout.write("#") #sys.stdout是標准輸出文件,調用write方法進行寫操作 sys.stdout.flush() #同步刷寫到磁盤 time.sleep(0.04) #sleep一下,便於看到動態逐個輸出#的效果 輸出: 程序每次輸出一個#,直到達到30個為止,近似於軟件包安裝進度條
- truncate()
傳入參數int n,將文件從文件頭開始截取n個字符。注意截取的起始位置永遠都是文件頭(即使通過seek跳轉指針也不例外),另外打開文件時必須具有寫權限(因為截取后直接修改了源文件的內容)。
f = open("test.txt", "r+", encoding="utf-8") print(f.read()) print(f.tell()) print("第一次截取") f.truncate(30) print("第二次截取") f.seek(10) #跳轉文件指針以便驗證 f.truncate(10) f.seek(0) print(f.read()) f.close() 輸出: Seems the love I've ever known 看來,過去我所知道的愛情 Has always been the most destructive kind 似乎總是最具有毀滅性的那種 Guess that's why now 或許,那就是為什么 203 第一次截取 第二次截取 Seems the #最后輸出的文件內容表明第二次截取時仍然是從文件頭開始的,盡管我們跳轉了文件指針
- close()
關閉打開的文件句柄,切記切記! - closed
判斷打開的文件句柄是否已關閉,返回布爾值。
f = open("test.txt", "r+", encoding="utf-8") print(f.closed) f.close() print(f.closed) 輸出: False True
四、關於文件的編輯
如果要對文件的內容進行編輯處理,目前存在以下兩種思路:
(1)加載文件到內存后修改保存(類似於vim),適用於小文件
(2)讀取源文件內容並修改,然后寫入另外的新文件,如果需要則再把新文件重命名后覆蓋源文件
示例程序:
f = open("test.txt", "r", encoding="utf-8") #源文件 f2 = open("test2.txt", "w+", encoding="utf-8") #寫入修改內容后保存的新文件 print("=========") print(f2.read()) print("=========") for line in f: #逐行讀取源文件內容 if "昨日當我年少輕狂" in line: line = line.replace("昨日當我年少輕狂", "明日當我初露鋒芒") f2.write(line) #不管源文件中的內容是否需要修改,統統寫入新文件 f2.seek(0) print(f2.read()) print("======") f.seek(0) print(f.read()) f.close() f2.close() 輸出: ========= ========= Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 明日當我初露鋒芒 ====== Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 昨日當我年少輕狂
五、with語句的應用
前面反復強調打開一個文件后要記得及時關閉以釋放資源,python很人性化地設計了一個自動關閉已經打開的文件的功能,這就是with語句。具體用法如下:
with open("file", "openMode", encoding="utf-8") as FileOBject: 斜體表示傳入的參數
示例:
with open("test.txt", "r", encoding="utf-8") as f: print("第一次判斷:") print(f.closed) for line in f: print(line) print("####") f.seek(0) print("第二次判斷:") print(f.closed) print(f.read(10)) print("第三次判斷:") print(f.closed) 輸出: 第一次判斷: False Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 昨日當我年少輕狂 #### 第二次判斷: False Somehow, i 第三次判斷: True
通過以上程序可看出,只要with代碼塊沒有執行完畢(注意結尾有冒號,類似於循環結構語法),文件就一直保持open狀態,一旦跳出with則自動關閉。
小技巧:
實際應用中可能需要操作多個文件,python 2.7以上版本可以在一個with語句中逐個接上多個open as,語法如下:
with open("file1", "r", encoding="utf-8") as f1, open ("file2", "r+", encoding="utf-8") as f2:
為了遵循pep8編碼規范,如一行超出了80個字符,則需要換行處理,未超出之前也可以自由換行以提高代碼的可讀性:
with open("file1", "r", encoding="utf-8") as f1, \
open ("file2", "r+", encoding="utf-8") as f2:
示例:
with open("test.txt", "r", encoding="utf-8") as f1,\ open("test2.txt", "r+", encoding="utf-8") as f2: print(f1.read()) print("") f2.write("我是第二個文件") f2.seek(0) print(f2.read()) 輸出: Somehow, it seems the love I knew was always the most destructive kind 不知為何,我經歷的愛情總是最具毀滅性的的那種 Yesterday when I was young 昨日當我年少輕狂 我是第二個文件