第十五篇 Python之文件處理


一 文件操作

  1.  介紹

計算機系統分為:計算機硬件,操作系統,應用程序三部分。

我們用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()
View Code

  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']
View Code

2. w 模式 寫操作

f = open('test.txt','w',encoding = 'utf-8')

f.read()   
# 報錯  io.UnsupportedOperation: not readable
# 原因:句柄指定的是寫方法,不可讀的。
View Code
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
View Code

 3. a 模式 追加操作

a 模式本身就是寫模式,只不過是以追加的方式寫到文件的最后。

f=open('test1','a',encoding='utf-8')
f.write('寫到文件最后')

#結果
11111111
222222222
333
4444
555
555
6666
555
6666
寫到文件最后
View Code

其他文件操作模式:

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])  

# 修改完之后,文件內容如下:
你是誰,
View Code

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)
View Code

文件的高級操作:

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
你好啊林師傅
View Code

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
楊件
View Code

6-3  ab模式

f=open('test22.py','ab') #b的方式不能指定編碼
f.write('楊老二'.encode('utf-8'))
# a追加,a不代表在最后一行追加寫入,而是代表在文件最后的光標位置開始追加寫入
# 舉例:最后一行有換行,那光標就是定位在有文字內容的下一行的

# 結果
1111
楊件
楊老二
View Code

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')


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM