一 文件操作
- 介紹
計算機系統分為:計算機硬件,操作系統,應用程序三部分。
我們用python或其他語言編寫的應用程序若想要把數據永久保存下來,必須要保存於硬盤中,這就涉及到應用程序要操作硬件,眾所周知,應用程序是無法直接操作硬件的,這就用到了操作系統。操作系統把復雜的硬件操作封裝成簡單的接口給用戶/應用程序使用,其中文件就是操作系統提供給應用程序來操作硬盤虛擬概念,用戶或應用程序通過操作文件,可以將自己的數據永久保存下來。
有了文件的概念,我們無需再去考慮操作硬盤的細節,只需要關注操作文件的流程:
#1. 打開文件,得到文件句柄並賦值給一個變量 #2. 通過句柄對文件進行操作 #3. 關閉文件
什么是句柄?
1.代碼的運行是在內存中運行的,在下一步要讀文件內容,而文件內容在硬盤上,所以需要將硬盤上的文件加載到內存中,open()函數則提供了這個功能,相當於向操作系統要了一個魚網,這個魚網
就是句柄。open實際上是在調操作系統,由操作系統最終給你返回一個魚網,即句柄
將句柄賦值給一個變量f,有了這個f句柄,就可以操作硬盤的文件了。
2.有了魚網(f),下一步想要什么魚,就用魚網撈就行了,或者想往操作系統里放什么魚,都可以通過魚網進行。對應的代碼就是read,write等方法,如:f.read()
3.關閉文件.實際上關閉的不是文件,而是將魚網回收。如果不關閉,那操作系統就一直給你發放魚網,就會占用操作系統的資源一直不釋放。
重點強調的
#強調第一點: 打開一個文件包含兩部分資源:操作系統級打開的文件+應用程序的變量。在操作完畢一個文件時,必須把與該文件的這兩部分資源一個不落地回收,回收方法為: 1、f.close() #回收操作系統級打開的文件 2、del f #回收應用程序級的變量 其中del f一定要發生在f.close()之后,否則就會導致操作系統打開的文件還沒有關閉,白白占用資源, 而python自動的垃圾回收機制決定了我們無需考慮del f,這就要求我們,在操作完畢文件后,一定要記住f.close() 為了避免忘記f.close(),推薦傻瓜式操作方式:使用with關鍵字來幫我們管理上下文 with open('a.txt','w') as f: pass with open('a.txt','r') as read_f,open('b.txt','w') as write_f: data=read_f.read() write_f.write(data) 強調第一點:資源回收 #強調第二點: f=open(...)是由操作系統打開文件,那么如果我們沒有為open指定編碼,那么打開文件的默認編碼很明顯是操作系統說了算了,操作系統會用自己的默認編碼去打開文件,在windows下是gbk,在linux下是utf-8。 這就用到了上節課講的字符編碼的知識:若要保證不亂碼,文件以什么方式存的,就要以什么方式打開。 f=open('a.txt','r',encoding='utf-8') 強調第二點:字符編碼
2. Python 中的進行文件處理的流程示例

#一. 打開文件,得到文件句柄並賦值給一個變量 f=open('a.txt', 'r', encoding='utf-8') 打開文件的過程中需要注意的幾個問題: # 1.文件路徑:示例中的文件沒有加路徑,是因為該文件與python文件是在同一個目錄的;如果不在同一個目錄就需要指定文件路徑 # 2.默認打開模式就為r # 3.open不是找Python的編碼,open函數默認的是檢索你所用的操作系統的編碼。所以打開文件為避免異常,需要指定編碼格式,文件用什么編碼格式,打開文件就需要指定對應的編碼格式。 # 4.如果Pycharm寫的文件默認保存為 utf-8; # 5.mac默認的文件編碼是utf-8 #二. 通過句柄對文件進行操作 data=f.read() #三. 關閉文件 f.close()
3. f = open('a.txt','r')的過程分析
#1、由應用程序向操作系統發起系統調用open(...) #2、操作系統打開該文件,並返回一個文件句柄給應用程序 #3、應用程序將文件句柄賦值給變量f
二 打開文件的模式
文件句柄 = open('文件路徑', '模式')
模式可以是下面的一種或者他們之間的組合:
Character | Meaning |
‘r' | open for reading (default) |
‘w' | open for writing, truncating the file first |
‘a' | open for writing, appending to the end of the file if it exists |
‘b' | binary mode |
‘t' | text mode (default) |
‘+' | open a disk file for updating (reading and writing) |
‘U' | universal newline mode (for backwards compatibility; should not be used in new code) |

#1. 打開文件的模式有(默認為文本模式): r, 只讀模式【默認模式,文件必須存在,不存在則拋出異常】 w, 只寫模式【不可讀;不存在則創建;存在則清空內容】 a, 只追加寫模式【不可讀;不存在則創建;存在則只追加內容】 #2. 對於非文本文件,我們只能使用b模式,"b"表示以字節的方式操作(而所有文件也都是以字節的形式存儲的,使用這種模式無需考慮文本文件的字符編碼、圖片文件的jgp格式、視頻文件的avi格式) rb wb ab 注:以b方式打開時,讀取到的內容是字節類型,寫入時也需要提供字節類型,不能指定編碼 #3. 了解部分 "+" 表示可以同時讀寫某個文件 r+, 讀寫【可讀,可寫】 w+,寫讀【可讀,可寫】 a+, 寫讀【可讀,可寫】 x, 只寫模式【不可讀;不存在則創建,存在則報錯】 x+ ,寫讀【可讀,可寫】 xb # 回車與換行的來龍去脈 http://www.cnblogs.com/linhaifeng/articles/8477592.html # U模式 'U' mode is deprecated and will raise an exception in future versions of Python. It has no effect in Python 3. Use newline to control universal newlines mode. # 總結: 在python3中使用默認的newline=None即可,換行符無論何種平台統一用\n即可 了解U模式與換行符 三 操作文件的方法 三種最常用,最純凈的文件操作模式:r、w、a 模式 1. r 模式 讀操作 f = open('test.txt','r',encoding = 'utf-8') # r 模式,讀的方法 # data = f.read() # read代表讀取文件全部 print(f.readable()) # True 表示該文件可讀 print('第一行',f.readline()) # 一次讀一行。即使文件里有多行,運行一次也只執行一行。 print('第二行',f.readline()) print('第三行',f.readline()) print('第四行',f.readline()) # 結果 第一行 你好啊, 第二行 adb 第三行 djfafjad 第四行 世界你好 # readline()讀取文件的結果,每行之間都空着一行,這是因為每行結尾的時候都有個回車空格導致,解決方法如下: print('1行',f.readline(),end = "") print('2行',f.readline(),end = "") print('3行',f.readline(),end = "") print('4行',f.readline(),end = "") # 結果 , 就把上面的空行給去掉了。 1行 你好啊, 2行 adb 3行 djfafjad 4行 世界你好 print(f.readlines()) # 讀取文件內容,並放到一個列表里 # 結果 ['你好啊,\n', 'adb\n', 'djfafjad \n', '世界你好\n']
2. w 模式 寫操作

f = open('test.txt','w',encoding = 'utf-8') f.read() # 報錯 io.UnsupportedOperation: not readable # 原因:句柄指定的是寫方法,不可讀的。

f = open('test.txt','w',encoding = 'utf-8') # w 寫模式,都是新建一個空文檔,直接覆蓋原來的文件。 # test.txt 文件存在,直接運行上面的文件,會清空test.txt文件 f1 = open('test1.txt','w',encoding = 'utf-8') # test1.txt文件不存在,會直接新建一個test1.txt空文件 f1.write('1111\n') f1.write('1232\n') f1.write('1232\n') f1.write('1111\nfjdfl\njadlj') f1.write('接着上面的行寫,不會換行') # 如過寫文件內容的時候沒有加\n換行符,就會直接緊挨着寫 # 寫到test1.txt文件里的結果 1111 1232 1232 1111 fjdfl jadlj接着上面的行寫,不會換行 print(f1.writable()) # True 代表可寫入 # writelines 是傳入一個列表,列表里寫上要寫入文件的內容 f1.writelines(['555\n','666\n']) # 寫到test1.txt文件里的結果 1111 1232 1232 1111 fjdfl jadlj接着上面的行寫,不會換行 555 666 # 注意: 讀取、寫入文件里的內容全都是字符串,如果輸入非字符串類型就會報錯 f1.write(3) # TypeError: write() argument must be str, not int
3. a 模式 追加操作
a 模式本身就是寫模式,只不過是以追加的方式寫到文件的最后。

f=open('test1','a',encoding='utf-8') f.write('寫到文件最后') #結果 11111111 222222222 333 4444 555 555 6666 555 6666 寫到文件最后
其他文件操作模式:
4. r+ 模式:既能讀,又能寫

示例1: # 1. 先打開文件 f = open('rj','r+',encoding= 'utf-8') # 2. 再讀取文件 data = f.read() print(data) # 結果 你是誰 # 3. 最后寫入文件(此時經過讀取,光標已經定位在最后了,所以新寫入的內容都在原文件內容的最后面) f.write('123ssn') #結果 你是誰123ssn # 新寫入的123ssn為什么直接寫在了原文件內容的后面,而沒有換行呢? 因為原文件內容寫完 你是誰之后沒有按回車鍵進行換行,所以直接接着寫了。 如果在你是誰之后操作了回車換行,再寫入就新起了一行 # 結果 你是誰 123ssn 示例2: # 打開文件,沒有讀,直接寫 f = open('rj','r+',encoding= 'utf-8') f.write('sb') #結果 123ssn誰 # 123ssn為什么會寫在最前面?“你是”兩個字哪里去了? 文件根本就沒有修改這么一說,全都是覆蓋。 所以一打開文件,光標就停留在最開始的位置寫入是從光標所在的位置開始寫的,所以123ssn會寫在最前面,后面的內容也被覆蓋掉了。 # 那么我們平時常見的文件修改是怎么完成的呢? 首先,要改是通過軟件(word/txt,cat等)打開文件,將文件內容全部都加載到了內存當中 其次,在內存當中是可以修改文件的 最后,當文件修改完成后,還是需要通過軟件保存文件。而軟件會將內存中的文件全部覆蓋回去到硬盤上。 這就是我們看到的對文件的修改。 示例3:模擬文件的修改過程: 原文件的內容: 你是誰, who am i ? 1. 先打開了源文件 src_file = open('rj','r',encoding = 'utf-8') 讀的方式打開 2. 讀取文件內容並復制給一個變量,存在內存里 data = src_file.readlines() # 讀取的文件內容是個列表了 3. 關閉原文件 src_file.close() 4. 再打開了一個新的文件,因為剛才的文件內容已經讀取到且放在了內存里,所以此時再寫,就可以指定其他的編碼方式了 dst_f = open('rj' , 'w', encoding = 'gbk') 5. 模擬修改文件的,所以只需寫入第一行文件內容 dst_f.write(data[0]) # 修改完之后,文件內容如下: 你是誰,
5. with 關鍵字
上面的方法打開文件后,都需要專門寫個close()方法關閉文件,而使用with關鍵字則可以一步到,不用再單獨寫close()關閉文件了。

示例1: with open('rj', 'w', encoding='utf-8') as f: f.write('with方式打開文件\n') # open('rj', 'w', encoding='utf-8') as f 這個語句意思等於 f = open('rj', 'w', encoding='utf-8') # 只不過加上了with關鍵字,就不需要再專門寫個close()方法來關閉文件了 示例2:with 同時打開兩個文件 # Python里一行不要寫太長的代碼,可以通過 \ 來換行連接 # 需求:從原文件里讀到的內容,直接不做修改,寫到新的文件里 with open('rj', 'r', encoding='utf-8') as f, \ open('rj_new', 'w', encoding= 'utf-8') as dis_file: data = f.read() dis_file.write(data)
文件的高級操作:
Linux處理文件全部都是二進制的方式
6. 文件處理b模式
寫在最前:b的方式不能指定編碼,無論是rb,wb,ab都不能指定編碼
為什么要用二進制?
二進制代表處理數據的方式,並不代表最后的內容。文件默認處理就是以t模式進行,即文本模式,所以 r = rt, w = wt, a =at, 只是省略了t。
1. 文件並不僅僅是文本,還有圖片,視頻,音頻等,如果還以t的方式就處理不了,就需要以二進制的方式處理,即b方式處理,這是由文件的類型決定的。
2. 二進制這種方式可以跨平台,Linux,Windows,unit等各種操作系統,都是使用硬件(如硬盤)來處理數據,操作系統把文件交給硬盤,而硬盤存儲的都是二進制的內容,所以操作系統給硬盤的文件都得是二進制的。所以以二進制方式處理文件,你的文件就可以跨平台。
3. b 模式對Linux沒什么卵用,因為Linux默認就是二進制處理,b模式對Windows系統有用。
6-1 rb 模式

#rb模式 要讀取的test11.py里文件的內容如下: hello1 22222 33333 4444 你好啊林師傅 f=open('test11.py','rb',encoding='utf-8') # 結果:會報錯 ValueError: binary mode doesn't take an encoding argument # 原因:文件存儲在硬盤上是以二進制方式存儲的,而讀的時候就告訴了open函數,以 b(字節)的方式進行讀取的,讀取的就是原生的二進制,然后又指定編碼,這是什么意思?是無意義的了。 #所以,b的方式不能指定編碼 # 正確的讀取方式 f = open('test11.py','rb') #b的方式不能指定編碼,讀取就正確了 data = f.read() print(data) # 結果 b'hello1\r\n22222\r\n33333\r\n4444\r\n\xe4\xbd\xa0\xe5\xa5\xbd\xe5\x95\x8a\xe6\x9e\x97\xe5\xb8\x88\xe5\x82\x85' 結果解析: b:代表讀出來的是原生的字節形式 \r\n:Windows里的回車鍵就是以\r\n表示的,表示一個換行 \xe4\xbd\xa0\xe5\xa5\xbd\xe5\x95\x8a\xe6\x9e\x97\xe5\xb8\x88\xe5\x82\x85:因為讀取的就是二進制,所以中文在硬盤上就是這么存儲的。 # 那還是以b的方式讀取,就想看到轉譯后的人類可讀的文字,怎么辦? 將二進制轉換成字符串的形式就可以了。 文件往硬盤里存的時候,在內存里是字符串的形式,通過編碼之后,以字節的方式存在硬盤里的: 字符串(內存) -----> encoding ------> bytes(硬盤) 所以,想讓人能看懂,先從硬盤讀取,然后解碼到內存: bytes(硬盤) ----->decode ------> 字符串 f = open('test11.py','rb') data = f.read() print(data.decode('utf-8')) # # 結果 : 就變成人類可讀的語言了 hello1 22222 33333 4444 你好啊林師傅
6-2 wb模式

# 指定要以b模式寫文件 f=open('test22.py','wb') #b的方式不能指定編碼 # 方式1:通過bytes函數將字符串進行編碼,轉換成字節形式寫入文件 f.write(bytes('1111\n',encoding='utf-8')) # 把字符串轉換為字節寫入 # 方式2:字符串本身就具有encode方法,所以可以直接 .encode()進行編碼然后寫入 f.write('楊件'.encode('utf-8')) # 結果 1111 楊件
6-3 ab模式

f=open('test22.py','ab') #b的方式不能指定編碼 f.write('楊老二'.encode('utf-8')) # a追加,a不代表在最后一行追加寫入,而是代表在文件最后的光標位置開始追加寫入 # 舉例:最后一行有換行,那光標就是定位在有文字內容的下一行的 # 結果 1111 楊件 楊老二
7. 文件的其他操作方法
f = open('www.txt','w',encoding= 'gbk') # closed:查看文件是否關閉 print(f.closed) # False:表示文件是打開的, True:表示文件是關閉了 # 得到的是文件打開的編碼方式,而不是原文件的存儲編碼; 如果文件打開模式為b,則沒有該屬性
f = open('www.txt','w',encoding= 'gbk') print(f.encoding) # 這個方法指的是open()函數,打開文件時,encoding = "gbk" 這個編碼 # 如果真不知道文件編碼,怎么辦? # latin-1: 拉丁編碼方式,會最大程度的保證文件能正確讀出來---欠着,貌似還有不對的地方。 f = open('www.txt','r', encoding = 'latin-1') data = f.read() print(data)
f = open('www.txt','w',encoding= 'gbk') print(f.fileno()) # 文件描述符,沒啥用 print(f.errors) # 關於文件的報錯,沒啥大用 f.flush() # 刷新文件,將寫在內存里的文件刷新保存到硬盤上去(通過cmd終端演示能看到效果) f.isatty() # 看你用的是不是個終端設備 print(f.name) # www.txt 看文件的內容
文件內容如下: naidfna kfad jfalkdj愛看的積 分安監局卡的房間大 了借款方 f = open('www123.txt','r+',encoding= 'utf-8') # f.write("naidfna kfad \njfalkdj\n愛看的積分安監局卡的\n房間大了借款方 ") print(f.readlines()) # 結果 # ['naidfna kfad\n', 'jfalkdj愛看的積\n', '分安監局卡的房間大\n', '了借款方'] 這里為啥是\n, 因為換行符被python3多余處理了一下,去掉了\r(Windows) # 讓python保持原樣,文件里本來的換行符是什么就顯示什么,不做處理,需要在讀取的時候 指定 newline = '' # 讀取文件中真正的換行符號 f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '') # 結果,就顯示 \r\n了。 ['naidfna kfad\r\n', 'jfalkdj愛看的積\r\n', '分安監局卡的房間大\r\n', '了借款方']
8. 文件內光標移動(文件操作方法)
一: read(3):
1. 文件打開方式為文本模式時,代表讀取3個字符
2. 文件打開方式為b模式時,代表讀取3個字節
二: 其余的文件內光標移動都是以字節為單位如seek,tell,truncate
注意:
1. seek有三種移動方式0,1,2,其中1和2必須在b模式下進行,但無論哪種模式,都是以bytes為單位移動的
2. truncate是截斷文件,所以文件的打開方式必須可寫,但是不能用w或w+等方式打開,因為那樣直接清空文件了,所以truncate要在r+或a或a+等模式下測試效果
1. tell()方法 # 告訴當前文件光標所在的位置,因為文件存儲的都是二進制的形式,utf-8一個漢字占3個字節,一個換行也占3個字節(\r\n) f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '') f.readline() # 讀一行文件 print(f.tell()) # 14, 讀了上面文件內容一行后,當前文件光標的位置在第14個字節處 2. seek()方法 # seek():用來控制光標的移動, 0 ,1 ,2 等表示字節數 f.seek(0) f.seek(3) # utf-8一個漢字占3個字節,所以對漢字至少是3的倍數進行seek,否則就會報錯 # seek 默認是從文件開頭移動的 with open('seek.txt', 'r', encoding= 'utf-8') as f: print(f.tell()) # 0 剛打開文件,讀取的光標位置為0 print(f.seek(10)) # 10 光標移動了10字節 print(f.tell()) # 10 print(f.seek(3)) # 3 上面已經seek 10個字節了,這里為啥不是13呢? 還是前面說的,seek默認是從文件開頭移動光標的。 print(f.tell()) # 3 備注: 其實seek()模式是從0開始的,上面的 seek(10) 其實 等於 seek(10, 0) : 0 表示每次seek都是從文件開頭移動 seek移動是以字節的方式進行,而0是默認從開頭移動,0這種方式模式就是以b字節模式進行移動,所以不用指定b模式。 而以 1 這種相對位置進行seek,必須指定 b模式,而二進制還不能指定打開文件的編碼方式 # 基於相對位置的seek seek(10,1): 1 代表相對位置,相對於上一次光標移動的位置是10,那么下一次移動就從10這個相對位置開始再往后移動 with open('seek.txt', 'rb', encoding= 'utf-8') as f: print(f.seek(10, 1)) # 報錯 io.UnsupportedOperation: can't do nonzero cur-relative seeks:以相對位置進行seek,必須指定b模式 ValueError: binary mode doesn't take an encoding argument: 以b模式處理文件,就不能進行其他編碼 所以,seek的相對位置移動,正確的處理方法如下: with open('seek.txt', 'rb') as f: print(f.tell()) # 0 print(f.seek(10, 1)) # 10 print(f.tell()) # 10 print(f.seek(3,1)) # 13 print(f.tell()) # 13 最有用的seek,從文件末尾倒敘往前seek,然后讀取的時候,就是從光標所在位置開始,讀取后面的所有內容 文件內容: 你好\r\n hello\r\n 546\r\n 123\r\n # 注意,為了能看出效果,上面的換行符號用\r\n表示,實際是不可能這樣寫的。 windows下,換行符 = \r\n, 一個換行符就占2個字節 with open('seek.txt', 'rb') as f: print(f.tell()) # 0 print(f.seek(-10, 2)) # 倒序seek:從文件末尾開始往前seek10個字符 print(f.read()) # b'\r\n546\r\n123' 倒序seek有啥用嘞? 讀日志,因為看日志永遠都是看最新的日志,而最新的日子都在文件的最后面 # 讀取的日志文件的最后一行 # 1. 常規讀法 with open('日志文件', 'rb') as f: data = f.readlines() print(data[-1]) # 2. 用seek去讀 f=open('日志文件','rb') # 循環文件的推薦方式 for i in f: offs=-10 # # 定義一個偏移量,這個需要自己大概估算一下,日志文件行有多少個字節 while True: f.seek(offs,2) data=f.readlines() # 當data的長度大於1說明最后一行以及倒數第二行已經讀出來了,然后就break if len(data) > 1: print('最后一行是%s' %(data[-1].decode('utf-8'))) break offs*=2 # 不斷加大光標偏移量 f.close() 3. truncate() 方法 # 截取:代表從文件內容的開頭到第十個字節 # truncate 其實是寫方法,表示從0到第10個字節,這些內容截取保留下來,其他的文件內容都刪除掉 # 所以,文件的打開方式不能以w+、w 的方式打開,因為w+、w 一打開就把原文件清空了,那還截取什么呢?其他任何 r+,a+等等都可以,就w+不行。 f = open('www123.txt','r+',encoding= 'utf-8' ,newline = '') f.truncate(10) 4. read()方法 f.read(2) # 代表讀2個字符。一個漢字就是一個字符,一個英文字母也是一個字符,一個標點符號還是一個字符。 # 除了read()方法,其他所有的方法在處理文件的光標的時候,統一都是按照字節來處理
9 .文件的修改
文件的數據是存放於硬盤上的,因而只存在覆蓋、不存在修改這么一說,我們平時看到的修改文件,都是模擬出來的效果,具體的說有兩種實現方式:
方式一:將硬盤存放的該文件的內容全部加載到內存,在內存中是可以修改的,修改完畢后,再由內存覆蓋到硬盤(word,vim,nodpad++等編輯器)
import os with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f: data=read_f.read() #全部讀入內存,如果文件很大,會很卡 data=data.replace('alex','SB') #在內存中完成修改 write_f.write(data) #一次性寫入新文件 os.remove('a.txt') os.rename('.a.txt.swap','a.txt')
方式二:將硬盤存放的該文件的內容一行一行地讀入內存,修改完畢就寫入新文件,最后用新文件覆蓋源文件
import os with open('a.txt') as read_f,open('.a.txt.swap','w') as write_f: for line in read_f: line=line.replace('alex','SB') # 把 'alex' 字符串替換為 'SB’ write_f.write(line) os.remove('a.txt') os.rename('.a.txt.swap','a.txt')