@[toc]
??本章將進(jìn)一步,讓程序能夠與更大的外部世界交互:文件和流
序:編碼
??gbk 漢字編碼 還有個(gè)名字叫cp936,漢字占2個(gè)字節(jié),windows默認(rèn)的 中國用的
??utf-8 萬國碼 漢字占3字節(jié), python,linux 默認(rèn)
一、打開文件
??要打開文件,可使用函數(shù)open,它位于自動(dòng)導(dǎo)入模塊io中。函數(shù)open將文件名作為唯一必不可少的參數(shù),并返回一個(gè)文件對(duì)象。
open函數(shù)的基本語法如下:
open(file_name[,access_mode][,buffering])-
fele_name變量:是一個(gè)包含要訪問的文件名稱的字符串值。 -
access_mode變量:指打開文件的模式 -
buffering:如果buffering的值被設(shè)為0,就不會(huì)有寄存;如果buffering的值取1,訪問文件時(shí)就會(huì)寄存行;如果將buffering的值設(shè)為大于1的整數(shù),表示這就是寄存區(qū)的緩存;如果取負(fù)值,寄存區(qū)的緩沖大小就是系統(tǒng)默認(rèn)的值。
??如果當(dāng)前目錄中有一個(gè)名為somefile.txt的文本文件,則可像下面這樣打開它:
>>> f = open('somefile.txt')
??默認(rèn)創(chuàng)建在py文件所在的地方。
??如果文件位于其他地方,可指定完整路徑。如果找不到文件,就會(huì)報(bào)錯(cuò)。
>>> f = open(r'C:\Users\JiaNeng\Desktop\20班視頻\文件操作及異常\2.txt')
# open函數(shù)返回一個(gè)File對(duì)象。
>>> print(f.name) #C:\Users\JiaNeng\Desktop\20班視頻\文件操作及異常\2.txt
這里有幾個(gè)概念要弄清楚。
- 文件路徑:文件的路徑是指文件在計(jì)算機(jī)上的位置。文件路徑又分為絕對(duì)路徑和相對(duì)路徑。
- 絕對(duì)路徑:總是從根文件夾開始。比如在Windows環(huán)境下,一般從c盤、d盤等開始,c盤、d盤被稱為根文件夾,在該盤中的文件都得從根文件夾開始往下一級(jí)一級(jí)查找。在Linux環(huán)境下,一般從usr、home等根文件夾開始。比如在上面的示例程序中,path變量值就是一個(gè)絕對(duì)路徑,在文件搜索框中輸入絕對(duì)路徑可以直接找到該文件。
- 相對(duì)路徑:相對(duì)于程序當(dāng)前工作目錄的路徑。比如當(dāng)前工作文件存放的絕對(duì)路徑是
D:\python\workspace,如果使用相對(duì)路徑,就可以不寫這個(gè)路徑,用一個(gè).號(hào)代替這個(gè)路徑值。
>>> path = './test.txt'
??除了單個(gè)點(diǎn),還可以使用兩個(gè)點(diǎn)表示父文件夾(或上一級(jí)文件夾)。
1. 文本模式
??如果要通過寫入文本來創(chuàng)建文件,這種調(diào)用函數(shù)open的方式并不能滿足需求。為解決這種問題,可使用函數(shù)open的第二個(gè)參數(shù)。
??調(diào)用函數(shù)open時(shí),如果只指定文件名, 將獲得一個(gè)可讀取的文件對(duì)象。如果要寫入文件,必須通過指定模式來顯式地指出這一點(diǎn)。函數(shù)open的參數(shù)mode的可能取值有多個(gè)。
<center>表 函數(shù)open的參數(shù)mode的最常見取值 </center>
| 值 | 描述 |
|---|---|
| 'r' | 讀取模式(默認(rèn)值),文件不存在時(shí)會(huì)報(bào)錯(cuò) |
| 'w' | 寫入模式,文件存在會(huì)清空之前的內(nèi)容,文件不存在則會(huì)新建文件 |
| 'x' | 獨(dú)占寫入模式,文件存在會(huì)報(bào)錯(cuò),文件不存在會(huì)新建文件 |
| 'a' | 附加模式,不清空之前的文件,直接將寫入的內(nèi)容添加到后面 |
| 'b' | 二進(jìn)制模式(與其他模式結(jié)合使用) |
| 't' | 文本模式(默認(rèn)值,與其他模式結(jié)合使用) |
| '+' | 讀寫模式(與其他模式結(jié)合使用) |
| 'r' | 顯示地指定讀取模式的效果與根本不指定模式相同。 |
??'w'寫入模式讓你能夠?qū)懭胛募?,并在文件不存在時(shí)創(chuàng)建它。
??'x'獨(dú)占地寫入模式更進(jìn)一步,在文件已存在時(shí)引發(fā)FileExistsError異常。在寫入模式下打開文件時(shí),既有內(nèi)容將被刪除(截?cái)?,并從文件開頭處開始寫入;如果要在既有文件末尾繼續(xù)寫入,可使用附加模式。
'a'打開一個(gè)文件用于追加。如果該文件已存在,文件指針就會(huì)放在文件的結(jié)尾。如果該文件不存在,就創(chuàng)建新文件進(jìn)行寫入。
??當(dāng)參數(shù)帶上'b'時(shí),表示可以用來讀取一個(gè)二進(jìn)制文件。
??'+'可與其他任何模式任何模式結(jié)合起來使用,表示既可讀取也可寫入。例如,要打開一個(gè)文本文件進(jìn)行讀寫,可使用'r+'。請(qǐng)注意,'r+'和'w+'之間有個(gè)重要差別:后者截?cái)辔募?,而前者不?huì)這樣做。
??默認(rèn)模式為'rt',這意味著將把文件視為經(jīng)過編碼的Unicode文本,因此將自動(dòng)執(zhí)行解碼和編碼,且默認(rèn)使用UTF-8編碼。要指定其他編碼和Unicode錯(cuò)誤處理策略,可使用關(guān)鍵字參數(shù)encodeing和errors。這還將自動(dòng)轉(zhuǎn)換換行字符。默認(rèn)情況下,行以'\n'結(jié)尾,讀取時(shí)將自動(dòng)替換其他行尾字符('\r'或'\r\n');寫入時(shí)將'\n'替換為系統(tǒng)的默認(rèn)行尾字符。(os.linesep)。
??通常,Python使用通用換行模式。在這種模式下,后面將討論的readlines等方法能夠識(shí)別所有合法的換行符(\n,\r和\r\n)如果要使用這種模式,同時(shí)禁止自動(dòng)轉(zhuǎn)換,可將關(guān)鍵字參數(shù)newline設(shè)置為空字符串,如open(name, newline='')。如果要指定只將'\r'或'\r\n'視為合法的行尾字符,可將參數(shù)newline設(shè)置為相應(yīng)的行尾字符。這樣,讀取時(shí)不會(huì)對(duì)行尾字符進(jìn)行轉(zhuǎn)換,但寫入時(shí)將把'\n'替換為指定的行尾字符。
??如果文件包含非文本的二進(jìn)制數(shù)據(jù),如聲音剪輯片段或圖像,你肯定不希望執(zhí)行上述自動(dòng)轉(zhuǎn)換。為此,只需使用二進(jìn)制模式(如'rb')來禁用與文本相關(guān)的功能。
2. 緩沖
??open函數(shù)的第3個(gè)參數(shù)是可選擇的,該參數(shù)控制文件的緩存。如果該參數(shù)賦值為0或False,I/O就是無緩存的。如果是1或True,I/O就是有緩存的。大于1的整數(shù)代表緩存的大小(單位是字節(jié))。-1或小于0的整數(shù)代表使用默認(rèn)的緩存大小。
??緩存一般指的是內(nèi)存,計(jì)算機(jī)從內(nèi)存中讀取數(shù)據(jù)的速度遠(yuǎn)遠(yuǎn)大于從磁盤讀取數(shù)據(jù)的速度,一般內(nèi)存大小遠(yuǎn)小于磁盤大小,內(nèi)存的速度比較快,但資源比較緊張,所以這里有是否對(duì)數(shù)據(jù)進(jìn)行緩存的設(shè)置。
I/O在計(jì)算機(jī)中指Input/Output,也就是輸入和輸出。由于程序和運(yùn)行時(shí)數(shù)據(jù)在內(nèi)存中駐留,由CPU這個(gè)超快的計(jì)算核心執(zhí)行,涉及數(shù)據(jù)交換的地方通常是磁盤、網(wǎng)絡(luò)等,因此需要I/O接口。
??比如打開瀏覽器,訪問百度首頁,瀏覽器需要通過網(wǎng)絡(luò)I/O獲取百度網(wǎng)頁。瀏覽器首先會(huì)發(fā)送數(shù)據(jù)給百度服務(wù)器,告訴它我想要首頁的HTML,這個(gè)動(dòng)作是往外發(fā)數(shù)據(jù),叫Output;隨后百度服務(wù)器把網(wǎng)頁發(fā)過來,這個(gè)動(dòng)作是從外面接收數(shù)據(jù),叫Input。通常,程序完成I/O操作會(huì)有Input和Output兩個(gè)數(shù)據(jù)流。
二、文件的基本方法
??本章介紹文件對(duì)象的一些基本方法以及其他類似于文件的對(duì)象(有時(shí)稱為流)。類似于文件的對(duì)象支持文件對(duì)象的一些方法,如支持read或write,或者兩者都支持。在開始之前,首先需要了解一下流的概念。
??I/O編程中,流(Stresm)是一個(gè)很重要的概念??梢园蚜飨胂癯梢粋€(gè)水管,數(shù)據(jù)就是水管里的水,但是只能單向流動(dòng)。Input Stream就是數(shù)據(jù)從外面(磁盤、網(wǎng)絡(luò))鎏金內(nèi)存,Output Stream就是數(shù)據(jù)從內(nèi)存流到外面去。瀏覽網(wǎng)頁時(shí),瀏覽器和服務(wù)器之間至少需要建立兩根水管,才能既發(fā)送數(shù)據(jù)又接收數(shù)據(jù)。
1. 讀取和寫入
??文件最重要的功能就是提供和接收數(shù)據(jù)。如果有一個(gè)名為f的類似于文件的對(duì)象,可使用f.write來寫入數(shù)據(jù),還可使用f.read來讀取數(shù)據(jù)。與Python的其他大多數(shù)功能一樣,在哪些東西可用作數(shù)據(jù)方面,也存在一定的靈活性,但在文本和二進(jìn)制模式下,基本上分別將str和bytes類用作數(shù)據(jù)。
??每當(dāng)調(diào)用f.write(string)時(shí),你提供的字符串都將寫入到文件中既有內(nèi)容的后面。
>>> f.open(somefile.txt', 'w')
>>> f.write('Hello, ') #7
>>> f.write('Word!') 6
>>> f.close()
??請(qǐng)注意:使用完文件后,我調(diào)用了方法close。
??讀取也一樣簡(jiǎn)單,只需告訴流你要讀取多少個(gè)字符(在二進(jìn)制模式下是多少字節(jié))
>>> f.open(somefile.txt', 'r')
>>> f.read(4) #'Hell'
>>> f.read() #'o, World!'
??首先,指定了要讀取4個(gè)字符。接下來,沒有指定讀取字節(jié)數(shù)時(shí),read方法會(huì)讀取打開文件中的所有字節(jié)。把所有內(nèi)容都放在字符串里包括換行符\n。讀到最后一行,再讀就輸出空字符串。
2. 使用管道重定向輸出
??在bash等shell中,可依次輸入多個(gè)命令,并使用管道將它們鏈接起來。
$ cat somefile.txt | python somescript.py | sort
這條管道線包含三個(gè)命令。
-
cat somefile.txt:將文件somefile.txt的內(nèi)容寫入到標(biāo)準(zhǔn)輸出(sys.stdout) -
python somescript.py:執(zhí)行Python腳本somescript。這個(gè)腳本從其標(biāo)準(zhǔn)輸入中讀取,并將結(jié)果寫入到標(biāo)準(zhǔn)輸出 -
sort:讀取標(biāo)準(zhǔn)輸入(sys.stdin)中的所有文本,將各行按字母順序排序,并將結(jié)果寫入到標(biāo)準(zhǔn)輸出。
管道是將一個(gè)命令的標(biāo)準(zhǔn)輸出鏈接到下一個(gè)命令的標(biāo)準(zhǔn)輸入。管道字符(|)。因此,somscript.py從其sys.stdin中讀取數(shù)據(jù),并將結(jié)果寫入到其sys.stdout
# somescript.py
import sys
text = sys.stdin.read()
words = text.split()
wordcount = len(words)
print('Wordcount:', wordcount)
3. 隨機(jī)存取
??在本章中,我將文件都視為流,只能按順序從頭到尾讀取。實(shí)際上,可在文件中移動(dòng),之訪問感興趣的部分(稱為隨機(jī)存取)。為此,可使用可使用文件對(duì)象的兩個(gè)方法:seek和tell。
??方法seek(offset, whence=0)將當(dāng)前位置(執(zhí)行讀取或?qū)懭氲奈恢?移到offset和whence指定的地方。
??參數(shù)offset指定了字節(jié)(字符)數(shù),而參數(shù)whence默認(rèn)為0,這意味著偏移量是相對(duì)于文件開頭的(偏移量不能為負(fù)數(shù))。參數(shù)whence還可設(shè)置為1或2,其中前者表示相對(duì)于當(dāng)前位置進(jìn)行移動(dòng)(偏移量可以為負(fù)),而后者表示相對(duì)于文件末尾進(jìn)行移動(dòng)。返回值為新的位置。
??請(qǐng)看下面示例:
>>> f = open(r'C\text\somefile.txt','w')
>>> f.write('01234567890123456789') #20
>>> f.seek(5, 0) #5 指針從開頭偏移5
>>> f.write('Hello, World!') #13
>>> f.close()
>>> f = open(r'C\text\somefile.txt','w')
>>> f.read() #'01234Hello, World!89'
??方法tell()返回當(dāng)前位于文件的什么位置。
>>> f = open(r'C\text\somefile.txt','w')
>>> f.read(3) #'012'
>>> f.read(2) #'34'
>>> f.tell() #5
4. 讀取和寫入行
??與其逐個(gè)讀取流中的字符,不如成行地讀取。Python為我們提供了readline()、readlines()和writelines()等方法用于行操作,例如:
path = './test.txt'
f_name = open(path,'w')
f_name.write('Hello,World!\n') #13
f_name = open(path,'a')
f_name.write('welcome!') #8
f_name = open(path,'r')
f_name.readline() # 'Hello,World!\n'
??要讀取一行(從當(dāng)前位置到下一個(gè)分行符的文本),可使用方法readline,換行符為\n。
??調(diào)用這個(gè)方法時(shí),可不提供任何參數(shù)(在這種情況下,將讀取一行并返回它;也可提供一個(gè)非負(fù)整數(shù),指定readline最多可讀取多少個(gè)字符。readline方法如果返回一個(gè)空字符串,說明已經(jīng)讀取到最后一行了。
??如果將上面示例的最后一行改為:
f_name.readlines() #['Hello world!\n','welcome']
??要讀取文件中的所有行,并以列表的方式返回它們,可使用方法readlines。列表中的每個(gè)字符串就是文本中的每一行,并且換行符也會(huì)被輸出。
??readlines方法可以傳入數(shù)值參數(shù),當(dāng)傳入的數(shù)值小于等于列表中一個(gè)字符串的長(zhǎng)度值時(shí),該字符串會(huì)被讀??;當(dāng)傳入小于等于0的數(shù)值時(shí),所有字符都會(huì)被讀取。例如:
path = './test.txt'
f_name = open(path,'w')
str_list = ['Hello world!\n','welcome!\n', 'welcome!\n']
f_name.writelines(str_list)
f_name.flush() #刷新:將緩存中的內(nèi)容寫到文件中
# close() #close也有這種效果,但一般不這么用
f_name = open(path,'r')
f_name.read() # 'Hello world!\nwelcome!\nwelcome!\n'
f_name = open(path,'r')
f_name.readlines() # ['Hello world!\n', 'welcome!\n', 'welcome!\n']
??方法writelines與readlines相反:接受一個(gè)字符串列表(實(shí)際上,可以是任何序列或可迭代對(duì)象),并將這些字符都寫入到文件或流中。請(qǐng)注意,寫入時(shí)不會(huì)添加換行符,因此你必須自行添加。另外,沒有方法writeline,因?yàn)榭梢允褂脀irte。
5. 關(guān)閉文件
??別忘了調(diào)用方法close將文件關(guān)閉。關(guān)閉文件沒有壞處,在有些操作系統(tǒng)和設(shè)置中,還可避免無意義地鎖定文件以防修改。另外,這樣做還可避免用完系統(tǒng)可能指定的文件打開配額。
??對(duì)于寫入過的文件,一定要將其關(guān)閉,因?yàn)镻ython可能緩沖你寫入的數(shù)據(jù)(將數(shù)據(jù)暫時(shí)存儲(chǔ)在某個(gè)地方,以提高效率)。因此如果程序因某種原因崩潰,數(shù)據(jù)可能根本不會(huì)寫入到文件中。如果要重置緩沖,讓所做的修改反映到磁盤文件中,但又不想關(guān)閉文件,可使用方法flush。然而,需要注意的是,根據(jù)所使用的操作系統(tǒng)和設(shè)置,flush可能處于鎖定考慮而禁止其他正在運(yùn)行的程序訪問這個(gè)文件。
??在讀寫文件的過程中,出現(xiàn)異常的概率還是挺高的,特別對(duì)于大文件的讀取和寫入,出現(xiàn)異常更是家常便飯。在讀或?qū)懳募倪^程中,用try語句捕獲可能出現(xiàn)的異常。在捕獲異常前有一個(gè)動(dòng)作要執(zhí)行,就是使用close方法關(guān)閉文件。
??要確保文件得以關(guān)閉,可使用一條try/finally語句,并在finally子句中調(diào)用close。
# 在這里打開文件
try:
# 將數(shù)據(jù)寫入到文件中
finally:
file.close()
??如果每次都要這么寫,就會(huì)很繁瑣。實(shí)際上,有一條專門為此設(shè)計(jì)的語句,那就是with語句,它能自動(dòng)幫我們調(diào)用close方法。
with open("somefile.txt") as somefile:
do_something(somefile)
??with語句讓你能夠打開文件并將其賦給一個(gè)變量(這里是somefile)。在語句體中,你將數(shù)據(jù)寫入文件(還可做其他事情)。到達(dá)該語句末尾時(shí),將自動(dòng)關(guān)閉文件,即便出現(xiàn)異常也是如此。
(1) 上下文管理器
??with語句實(shí)際上是一個(gè)非常通用的結(jié)構(gòu),允許你是用那個(gè)所謂的上下文管理器。上下文管理器是支持兩個(gè)方法的對(duì)象:__enter__和__exit__。
-
方法__enter__不接受任何參數(shù),在進(jìn)入with語句時(shí)被調(diào)用,其返回值被賦給關(guān)鍵字as后面的變量。 -
方法__exit__接受三個(gè)參數(shù):異常類型、異常對(duì)象和異常跟蹤。它在離開方法時(shí)被調(diào)用(通過前述參數(shù)將引發(fā)的異常提供給它)。如果__exit__返回False,將抑制所有的異常。
??文件也可用作上下文管理器。它們的方法__enter__返回文件對(duì)象本身,而方法__exit__關(guān)閉文件。
6. 文件重命名
??在應(yīng)用程序的過程中,我們可能需要程序幫助我們重命名某個(gè)文件的名字,而不是通過手動(dòng)的方式進(jìn)行。
??Python的os模塊為我們提供了rename方法,即文件重命名。使用這個(gè)方法需要導(dǎo)入os模塊。??rename方法的語法如下:
os.rename(current_file_name,new_file_name)
??該方法沒有返回值。若文件不在當(dāng)前目錄下,則文件名要帶上絕對(duì)路徑。
open(./test1.txt/','w')
os.rename('test1.txt','test2.txt')
??若之前已經(jīng)創(chuàng)建了名為test1的文件,則將文件名更改為test2;若之前沒有創(chuàng)建test1,則先創(chuàng)建test1,再更改名字。
7. 文件刪除
??Python的os模塊為我們提供了remove方法,即刪除文件。
??remove方法的語法如下:
os.remove(file_name)
??若文件不在當(dāng)前目錄下,則文件名要帶上絕對(duì)路徑。
??該方法沒有返回值。
??該方法只能刪除已經(jīng)存在的文件,文件不存在就會(huì)拋異常。
8. 使用文件的基本方法
??假如文件somefile.txt包含以下文本,可對(duì)其執(zhí)行哪些操作呢?
Welcome to this file
There is nothing here except
This stupid haiku
??首先是read(n)
>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> f.read(7) #'Welcome'
>>> f.read(4) #' to '
>>> f.close()
??接下來是read()
>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> print(f.read())
Welcome to this file
There is nothing here except
This stupid haiku
>>> f.close()
??下面是readline()
>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> for i in range(3):
print(str(i) + ':' + f.readline(), end='')
0:Welcome to this file
1:There is nothing here except
2:This stupid haiku
>>> f.close()
??最后是readlines()
>>> import pprint
>>> pprint.pprint(open(r'C:\Users\MIC\Desktop\somefile.txt').readlines())
['Welcome to this file\n',
'There is nothing here except\n',
'This stupid haiku\n']
>>> f.close()
??下面來嘗試寫入,首先是write(string)
>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt','w')
>>> f.write('this \nis no\nhaiku') #17
>>> f.close()
??修改后的文本文件
this
is no
haiku
??最后是writelines(list)
>>> f = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> lines = f.readlines()
>>> f.close()
>>> lines[1] = "isn't a\n"
>>> f=open(r'C:\Users\MIC\Desktop\somefile.txt','w')
>>> f.writelines(lines)
>>> f.close()
??再次修改后的文本文件
this
isn't a
haiku
三、迭代文件內(nèi)容
??所謂迭代,是指不斷重復(fù)一個(gè)動(dòng)作,直到這些動(dòng)作都完成為止。
??一種常見的文件操作是迭代其內(nèi)容,并在迭代過程中反復(fù)采取某種措施。
??在本節(jié)的所有示例中,我都將使用一個(gè)名為process的虛構(gòu)函數(shù)來表示對(duì)每個(gè)字符串或行所做的處理,你可以用自己的喜歡的方式實(shí)現(xiàn)這個(gè)函數(shù)。其中,filename也是虛構(gòu)的。
??更有用的實(shí)現(xiàn)包括將數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)結(jié)構(gòu)中、計(jì)算總和、使用模塊re替換以及添加行號(hào)。
1.每次一個(gè)字符(或字節(jié))
??一種最簡(jiǎn)單的文件內(nèi)容迭代是在while循環(huán)中使用方法read。例如,你可能想遍歷文件中的每個(gè)字符(在二進(jìn)制模式下是每個(gè)字節(jié)),為此可像下面這樣做:
with open(filename) as f:
while True:
char = f.read(1)
if not char:break
process(char)
??如果你每次讀取多個(gè)字符(字節(jié)),可指定要讀取的字符(字節(jié))數(shù)。
??該示例對(duì)寫入文件的每個(gè)字符都進(jìn)行循環(huán)了。這個(gè)程序運(yùn)行到文件末尾時(shí),read方法會(huì)返回一個(gè)空字符串,未執(zhí)行到空字符串前,返回的都是非空字符,表示布爾值為真。
2. 每次一行
??如果處理文本文件時(shí),你通常想做的是迭代其中的行,而不是每個(gè)字符。通過使用readline,可像迭代字符一樣輕松地迭代行。
with open(filename) as f:
while True:
line = f.readline()
if not line: break
process(line)
3. 讀取所有內(nèi)容
??如果文件不太大,可一次讀取整個(gè)文件;為此,可使用方法read并不提供任何參數(shù)(將整個(gè)文件讀取到一個(gè)字符串中),也可使用方法readlines(將文件讀取到一個(gè)字符串列表中,其中每個(gè)字符串都是一行)。
??請(qǐng)注意:除進(jìn)行迭代外,像這樣將文件內(nèi)容讀取到字符串或列表中也對(duì)完成其他任務(wù)很有幫助。
??例如,可對(duì)字符串應(yīng)用正則表達(dá)式,還可將列表存儲(chǔ)到某種數(shù)據(jù)結(jié)構(gòu)中供以后使用。
??迭代字符
with open(filename) as f:
for char in f.read():
process(char)
??迭代行
with open(filename) as f:
for line in f.readlines():
process(line)
4. 使用fileinput實(shí)現(xiàn)延遲行迭代
??我們前面介紹過read方法和readlines方法,這兩個(gè)方法不帶參數(shù)時(shí)將讀取文件中所有內(nèi)容,然后加載到內(nèi)存中。當(dāng)需要迭代大型文本中的行,此時(shí)使用這種方式將占用太多內(nèi)存,甚至直接使內(nèi)存溢出,從而導(dǎo)致執(zhí)行失敗。
??當(dāng)然,你可轉(zhuǎn)而結(jié)合使用while循環(huán)和readline,但在Python中,在可能的情況下,應(yīng)該首選for循環(huán),使用for循環(huán)意味著可以對(duì)任務(wù)進(jìn)行分隔操作,而不是一步到位。
??按行讀取文件時(shí),若能使用for循環(huán),則稱之為懶加載式迭代。你可使用一種名為延遲行迭代的方法—說它延遲是因?yàn)樗蛔x取實(shí)際需要的文本部分。
??請(qǐng)注意:模塊fileinput會(huì)負(fù)責(zé)打開文件,你只需給它提供一個(gè)文件名即可。這些操作被封裝在input方法內(nèi)部了。
import fileinput
for line in fileinput.input(filename):
process(line)
fileinput.input()它幫助迭代多個(gè)輸入流中的行,它返回一個(gè)可在for循環(huán)中進(jìn)行迭代的對(duì)象。
5. 文件迭代器
??文件實(shí)際上是可迭代的,這意味著可在for循環(huán)中直接使用它們來迭代行。
with open(filename) as f:
for line in f:
process(line)
??在這些迭代示例中,我都將文件用作了上下文管理器,以確保文件得以關(guān)閉。雖然這通常是個(gè)不錯(cuò)的主意,但只要不寫入文件,就并非一定要這樣做。如果你愿意讓Python去負(fù)責(zé)關(guān)閉文件,可進(jìn)一步簡(jiǎn)化這個(gè)示例,如下所示。
for line in open(filename):
process(line)
??在這里,我沒有將打開的文件賦給變量,因此沒法顯示地關(guān)閉它。
??請(qǐng)注意:sys.stdin也是可迭代的。因此要迭代標(biāo)準(zhǔn)輸入中的所有行,可像下面這樣做:
import sys:
for line in sys.stdin:
process(line)
??另外,可對(duì)迭代器做的事情基本上都可對(duì)文件做,如(使用list(open(filename)))將其轉(zhuǎn)換為字符串列表,其效果與使用readlines相同。
>>> f = open('somefile.txt','w')
>>> print('First', 'line', file = f)
>>> print('Second', 'line', file = f)
>>> print('Third', 'and final', file = f)
>>> f.close()
>>> lines = list(open('somefile.txt'))
>>> lines # ['First line\n', 'Second line\n', 'Third and final\n']
>>> first,second,third = open(r'C:\Users\MIC\Desktop\somefile.txt')
>>> first #'First line\n'
>>> second #'Second line\n'
>>> third #'Third and final\n'
??文件內(nèi)容
First line
Second line
Third and final
??print('a',file=f)將文本輸入到file-like對(duì)象中,可以是文件,數(shù)據(jù)流等等,默認(rèn)是sys.stdout
在這個(gè)示例中,需要注意如下幾點(diǎn)。
- 使用了print類似寫入文件,這將自動(dòng)在提供的字符串后面添加換行符。
- 對(duì)打開的文件進(jìn)行序列解包,從而將每行存儲(chǔ)到不同的變量中。(這種做法不常見,因?yàn)橥ǔ2恢牢募嗌傩?,但這演示了文件對(duì)象是可迭代的)
- 寫入文件后將其關(guān)閉,以確保數(shù)據(jù)得以寫入磁盤。(如你所見,讀取文件后并沒有將其關(guān)閉。這可能有點(diǎn)粗糙,但并非致命的。)
四、StringIO函數(shù)
??數(shù)據(jù)的讀取除了通過文件,還可以在內(nèi)存中進(jìn)行。Python中的io模塊提供了隊(duì)str操作的StringIO函數(shù)。
??要把str寫入StringIO,我們需要?jiǎng)?chuàng)建一個(gè)StringIO,然后像文件一樣寫入。操作示例如下:
from io import StringIO
io_val = StringIO()
io_val.write('hello')
print('say:',io_val.getvalue())
# say: hello
??getvalue()方法用于獲得寫入后的str。
??要讀取StringIO,還可以用str初始化StringIO,然后像文件一樣讀取。
from io import StringIO
io_val = StringIO('Hello\nWorld!\nWelcome!')
while True;
line = io_val.readline()
if line =='':
break
print('line value:',line.strip())
# line value: Hello
# line value: World!
# line value: Welcome!
五、序列化與反序列化
??在運(yùn)行程序的過程中,所有變量都在內(nèi)存中,我們把變量從內(nèi)存中變成可存儲(chǔ)或傳輸?shù)倪^程稱為序列化。我們可以把序列化后的內(nèi)容寫入磁盤,或者通過網(wǎng)絡(luò)傳輸?shù)絼e的機(jī)器上。反過來,把變量?jī)?nèi)容從序列化的對(duì)象重新讀到內(nèi)存里稱為反序列化。
- 序列化是指將數(shù)據(jù)結(jié)構(gòu)或?qū)ο筠D(zhuǎn)換成二進(jìn)制串的過程。
- 反序列化是指將序列化過程中生成的二進(jìn)制串轉(zhuǎn)換成數(shù)據(jù)結(jié)構(gòu)或?qū)ο蟮倪^程。
下面我們介紹Python中序列化和反序列化的方式。
1. 一般序列化與反序列化
??Python中的pickle模塊實(shí)現(xiàn)了基本數(shù)據(jù)序列和反序列化。
- 通過pickle模塊的序列化操作,能夠?qū)⒊绦蛑羞\(yùn)行的對(duì)象信息保存到文件中,從而永久存儲(chǔ)。
- 通過pickle模塊的反序列化操作,能夠從文件中創(chuàng)建上一次程序保存的對(duì)象。
??pickle模塊的基本接口如下:
pickle.dump(obf,file,[,protocol])
??例如:
import pickle
d = dict(name='xiao zhi',num = 1002)
print(pickle.dumps(d))
#b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x08\x00\x00\x00xiao zhiq\x02X\x03\x00\x00\x00numq\x03M\xea\x03u.'
pickle,dumps()方法把任意對(duì)象序列化成一個(gè)bytes,然后把這個(gè)bytes寫入文件。也可以使用另一種方法pickle.dumps(),直接把對(duì)象序列化后寫入一個(gè)文件中,程序如下:
try:
f_name = open('dump.txt','wb')
pickle.dump(d,f_name)
finally:
f_name.close()
??打開dump.txt文件,可以看到里面是一堆看不懂的內(nèi)容,這些都是python保存的對(duì)象內(nèi)部信息。
??既然已經(jīng)將內(nèi)容序列化到文件中了,使用文件時(shí)就需要把對(duì)象從磁盤讀到內(nèi)存??梢韵劝褍?nèi)容讀到一個(gè)bytes,然后用pickle.loads()方法反序列化對(duì)象;也可以直接用pickle.load()方法從一個(gè)對(duì)象中直接反序列化對(duì)象。從dump.txt文件中將序列化的內(nèi)容反序列化的代碼如下:
import pickle
try:
f_name = open('dump.txt','rb')
print('load result:',pickle.load(f_name))
finally:
f_name.close()
執(zhí)行結(jié)果如下:
load result: {'num':1002, 'name':'xiao zhi'}
??由執(zhí)行結(jié)果看到,變量的內(nèi)容被正確讀取出來了。不過,雖然內(nèi)容相同,但是對(duì)應(yīng)的變量已經(jīng)完全不同了。
??注意:pickle的序列化和反序列化只能用于Python,不同版本的python可能彼此都不兼容,因此pickle一般用于保存不重要的數(shù)據(jù),也就是不能成功反序列化也沒關(guān)系的數(shù)據(jù)。
2. JSON序列化與反序列化
??本節(jié)介紹的JSON方式是通用的。
??JSON是一種輕量級(jí)的數(shù)據(jù)交換格式,是基于ECMAScript的一個(gè)子集。
??Python3中可以使用json模塊對(duì)JSON數(shù)據(jù)進(jìn)行編碼解碼,包含以下兩個(gè)函數(shù)。
- json.dumps():對(duì)數(shù)據(jù)進(jìn)行編碼
- json.loads():對(duì)數(shù)據(jù)進(jìn)行解碼
??在JSON的編碼解碼過程中,Python的原始類型與JSON類型會(huì)相互轉(zhuǎn)換,具體的轉(zhuǎn)化對(duì)照如表所示;
<center>Python編碼為JSON類型</center>
| Python | JSON |
|---|---|
| dict | {} |
| list,tuple | [] |
| str | string |
| int or float | number |
| True/False | true/false |
| None | null |
<center>JSON解碼為Python類型</center>
| JSON | Python | Python | JSON |
|---|---|---|---|
| {} | dict | dict | {} |
| [] | list | list,tuple | [] |
| string | str | str | string |
| number | int or float | int or float | number |
| true/false | True/False | True/False | true/false |
| null | None | None | null |
??下面是JSON序列化與反序列化的示例:
import json
data = {'num':1002,'name':'xiao zhi'}
json_str = json.dumps(data)
print("Python 原始數(shù)據(jù):", data)
print("JSON 對(duì)象:", json_str)
??執(zhí)行結(jié)果如下:
{'num':1002,'name':'xiao zhi'} # Python 原始數(shù)據(jù):
{'num':1002,'name':'xiao zhi'} # JSON 對(duì)象:
??接著以上示例,我么可以將一個(gè)JSON編碼的字符串轉(zhuǎn)換為一個(gè)Python數(shù)據(jù)結(jié)構(gòu),代碼如下:
>>> data2 = jason.loads(json_str)
>>> data2 = json.loads(json_str)
>>> print("data2['name']:",data2['name']) #data2['name']: xiao zhi
>>> print("data2['num']:",data2['num']) #data2['num']: 1002
??如果要處理的是文件而不是字符串,就可以使用json.dump()和json.load()編碼、解碼JSON數(shù)據(jù)。
# 寫入JSON數(shù)據(jù)
with open('dump.txt','w') as f:
json.dump(data,f)
# 讀取數(shù)據(jù)
with OPen('dump.txt.','r') as f:
data = json.load(f)
六、調(diào)試
??當(dāng)我們讀取和寫入文件時(shí),經(jīng)常遇到和空白字符相關(guān)的問題。這些問題可能很難調(diào)試,因?yàn)榭崭?、制表符和換行符通常是不可見的,例如:
>>> str_val = '1 2\t 3\n 4 5'
>>> print(str_val)
1 2 3
4 5
??在這種情況下,Python為我們提供了repr函數(shù)。該函數(shù)可接收任何對(duì)象作為參數(shù),并返回對(duì)象的字符串表達(dá)形式。
>>> print(repr(str_val))
# '1 2\t 3\n 4 5'
??結(jié)果把字符原本輸出了。在實(shí)際應(yīng)用中,使用這種方式可以幫助調(diào)試。
??另一個(gè)經(jīng)常遇到的問題是不同系統(tǒng)使用不同的字符表示換行。有的系統(tǒng)使用換行符\n表示換行,有的系統(tǒng)使用回車符\r表示換行。如果我們編寫的代碼在不同系統(tǒng)上使用,這些不一致就可能導(dǎo)致異常。