- Python程序設(shè)計(jì)
- 張雪萍主編
- 7029字
- 2019-06-19 15:48:32
3.1 讀寫文件
3.1.1 文件對(duì)象聲明與基本操作
與其他語(yǔ)言處理文件類似,Python 文件操作的基本流程是:打開文件→對(duì)文件進(jìn)行讀、寫或其他操作→關(guān)閉文件。
1.文件的打開
使用內(nèi)置函數(shù)open來(lái)打開文件,語(yǔ)法格式如下:

它可以接受3個(gè)參數(shù),第一個(gè)是路徑(必不可少),第二個(gè)是操作的模式(可以指定它是讀或?qū)?,或者其他的操作),第三個(gè)是編碼,它可以指定當(dāng)前文件讀取或?qū)懭氲淖址幋a是什么。后面兩個(gè)參數(shù)是可以省略的。
1)路徑的寫法
【例 3-1】 假設(shè)在 C 盤的 path 路徑下有一個(gè)文本文件“data.txt”,則有兩種表達(dá)該路徑的方法。
方法一:'c:\\path\\data.txt',利用轉(zhuǎn)義字符指定路徑。由于“\”需要轉(zhuǎn)義,所以這里要用兩個(gè)“\\”。
方法二:r'c:\path\data.txt',利用“r”將路徑聲明為原始字符串。
以上兩種方式指定的都是絕對(duì)路徑,即絕對(duì)地址,而在某些情況下可以直接寫一個(gè)文件名('data.txt'),即前面不指定完整的路徑,以相對(duì)路徑的方式來(lái)表達(dá)。
在采用相對(duì)路徑表示時(shí),系統(tǒng)會(huì)在當(dāng)前的系統(tǒng)環(huán)境變量下去找有沒(méi)有同名的文件。不過(guò)建議大家盡量采用絕對(duì)路徑的表達(dá)方式,因?yàn)榻^對(duì)路徑寫得更清晰。
有時(shí)我們?cè)趯懘a時(shí),希望省略前面的磁盤目錄名稱,直接寫一個(gè)文件名,這時(shí)可以通過(guò)加載 os 模塊來(lái)改變當(dāng)前目錄[2]。Python 標(biāo)準(zhǔn)庫(kù)中的 os 模塊包含普遍的操作系統(tǒng)功能。如果希望程序與平臺(tái)無(wú)關(guān)的話,這個(gè)模塊是尤為重要的。os模塊提供了非常豐富的方法來(lái)處理文件和目錄??聪旅娴拇a:

以上異常信息顯示在當(dāng)前目錄下沒(méi)有找到要打開的文件,那么當(dāng)前的目錄是什么?可以通過(guò)導(dǎo)入模塊os來(lái)解決這個(gè)問(wèn)題。使用os模塊中的方法“getcwd()”可以獲得當(dāng)前的操作目錄。

瀏覽此目錄發(fā)現(xiàn)的確沒(méi)有“data.txt”文件。
通過(guò) os 模塊中的方法“chdir()”可以進(jìn)行目錄切換操作,這樣在打開或?qū)懳募r(shí)就可以省略文件所在的目錄路徑,只寫一個(gè)文件名稱即可。

os 模塊提供了很多對(duì)文件目錄進(jìn)行操作的方法,如判斷指定目錄下的指定文件是否存在、獲取指定目錄下的所有文件或子文件夾里的所有文件、刪除指定目錄下的文件、創(chuàng)建新目錄,還可以創(chuàng)建多級(jí)目錄等。os模塊的常用方法如表3-1所示。
可以在導(dǎo)入os模塊后,通過(guò)help(os)命令查閱更多的方法及它們的語(yǔ)法格式。
表3-1 os模塊的常用方法

注意:當(dāng)父目錄不存在時(shí),os.mkdir(path)不會(huì)創(chuàng)建目錄,但是 os.makedirs(path)會(huì)創(chuàng)建父目錄。
【例 3-2】 在 D 盤創(chuàng)建目錄“D:\456\123”。說(shuō)明,D 盤事先不存在文件夾“456”。

在D盤沒(méi)有文件夾“456”的情況下,利用方法makedirs可以在“456”文件夾下創(chuàng)建下級(jí)子文件夾“123”。

說(shuō)明os.makedirs()創(chuàng)建文件夾成功。
【例3-3】 將當(dāng)前目錄改為“D:\456\123”,然后再返回上一級(jí)目錄。

2)模式
模式用來(lái)指定打開文件時(shí)的操作方式。在 Python3 中,文本文件被當(dāng)作“Unicode”字符串對(duì)待,二進(jìn)制文件中的內(nèi)容則以字節(jié)的形式來(lái)操作。
利用全局函數(shù) open 聲明文件對(duì)象時(shí),在"模式"這個(gè)位置指定一個(gè)字符串來(lái)表示文件的打開模式。文件打開的主要模式如表3-2所示。
表3-2 文件打開的主要模式

使用 open 函數(shù)打開文件時(shí),如果"模式"處是'r',則表示以讀的方式來(lái)操作當(dāng)前的文件。如果要寫文件的話,把'r'換成'w'。如果想同時(shí)對(duì)這個(gè)文件進(jìn)行讀、寫操作,可以用'rw'。如果想在原有文件的基礎(chǔ)上追加一些內(nèi)容,則可以用'a'。
二進(jìn)制文件是以字節(jié)形式來(lái)操作的。其在模式指定上有些差別,需要在字母 r、w、a后面加上“b”,把它聲明為二進(jìn)制字節(jié)的方式。
本章只討論對(duì)文本文件進(jìn)行讀、寫等操作。
通過(guò)任意記事本在 D 盤的“D:\python\PythonEXample”目錄下創(chuàng)建一個(gè)文本文件“data.txt”,在保存文件時(shí)請(qǐng)注意編碼的選擇,如圖3-1所示。

圖3-1 保存文件時(shí)編碼的選擇
在Windows下的編碼是“ANSI”,它的字符是以“gbk”的形式保存的。如果想讓文本文件的兼容性更高,可以選擇“UTF-8”。目前默認(rèn)是“ANSI”。
接下來(lái)就可以用Python來(lái)對(duì)這個(gè)文本文件進(jìn)行操作了。
2.聲明文件
前面聲明其他變量時(shí)比較簡(jiǎn)單,比如聲明一個(gè) int 型變量“i”,或者聲明字符串變量“s”:

文件對(duì)象的聲明比較特殊。假設(shè)我們要聲明一個(gè)文件對(duì)象“f”,可以使用一個(gè)全局函數(shù)open來(lái)指定它的路徑、模式、編碼。

其中,第三個(gè)參數(shù)可以省略,例如:

在open函數(shù)調(diào)用完畢之后,f指向本地的某個(gè)文件,f相當(dāng)于將一個(gè)對(duì)象引用到文件(參考1.4.1節(jié))。可以通過(guò)type()來(lái)檢查f的類型。


測(cè)試發(fā)現(xiàn)f的類型是“_io.TextIOWrapper”,并不是我們認(rèn)為的“file”。這點(diǎn)請(qǐng)注意!
3.文件讀操作
當(dāng)使用 open 函數(shù)打開一個(gè)文件后,會(huì)返回一個(gè)文件對(duì)象,可以使用文件對(duì)象的方法完成對(duì)文件的讀、寫等操作。文件對(duì)象的常用方法如表3-3所示。
表3-3 文件對(duì)象的常用方法

說(shuō)明:對(duì)文件操作的時(shí)候究竟是字符還是字節(jié),取決于當(dāng)前操作文件的類型或讀取方式,若有“b”就是二進(jìn)制形式,讀取的是字節(jié);若沒(méi)有“b”就是文本形式,讀取的是字符。本章主要討論文本文件的讀、寫操作。
【例3-4】 讀取文件的所有內(nèi)容。

此時(shí),輸出的內(nèi)容前多了“\ufeff”,但通過(guò) print()輸出時(shí)不會(huì)出現(xiàn),所以不用理會(huì)。當(dāng)然,如果的確希望得到如下的結(jié)果:

那就需要修改open函數(shù)中的編碼參數(shù),如下:

觀察輸出的文件內(nèi)容,發(fā)現(xiàn)在換行的位置并沒(méi)有顯示為換行,而是通過(guò)一個(gè)轉(zhuǎn)義字符“\n”來(lái)顯示。換行顯示為“\n”,這是控制臺(tái)交互式方式下提示符的表現(xiàn)形式。如果希望在控制臺(tái)屏幕上顯示為換行結(jié)果,則可以使用 print()來(lái)輸出 f.read()的結(jié)果。但在上次使用完 f.read()后,若再次使用即第二次調(diào)用 f.read()時(shí)請(qǐng)注意,此時(shí)得不到想要的結(jié)果,得到的是空白。原因在于,它的內(nèi)部機(jī)制是文件讀取的時(shí)候有一個(gè)指針從開始移到結(jié)尾,read()結(jié)束之后,文件指針已經(jīng)移到文件尾了。當(dāng)再次執(zhí)行 read()時(shí),已經(jīng)沒(méi)有內(nèi)容可讀取了。針對(duì)這種情況,有兩種解決方法。
方法一:重新創(chuàng)建當(dāng)前文件的實(shí)例(重新進(jìn)行聲明),然后進(jìn)行讀取,但這樣比較麻煩。
方法二:把文件指針重新移到文件的開頭,即調(diào)用 f.seek(0)(它表示將指針移到文件的開頭,也就是第一個(gè)字符位置),然后重新調(diào)用f.read()來(lái)完成。
通過(guò)調(diào)用 f.seek(0)將文件指針重新移到文件最開始的位置,再次對(duì)文件進(jìn)行操作,這對(duì)于規(guī)模較小的文件是可以的。但若文件規(guī)模較大,這樣的讀取方式就不可行了,因?yàn)樗鼤?huì)占用內(nèi)存,讀取的效率不高,而且當(dāng)希望對(duì)讀取的文件內(nèi)容進(jìn)行進(jìn)一步的處理時(shí)比較麻煩。因此,可以考慮使用別的方法。
下面來(lái)看如何把一個(gè)規(guī)模較大的文件先讀入一個(gè)列表中,然后再針對(duì)每一行進(jìn)行處理。
【例3-5】 將文件所有行讀取到列表。

如果只是希望將文件每一行的內(nèi)容輸出,則可以通過(guò)一個(gè)更簡(jiǎn)單的方法來(lái)解決。因?yàn)?Python 將文件本身作為一個(gè)行序列,所以,通過(guò) for-in 遍歷循環(huán)可以直接輸出文件每一行的內(nèi)容。
對(duì)文件也可以不調(diào)用任何方法來(lái)完成這些操作,原因在于,聲明一個(gè)文件對(duì)象后,得到的文件指針是一個(gè)可迭代的對(duì)象,可用for-in遍歷循環(huán)對(duì)其進(jìn)行遍歷操作。

3.1.2 編碼問(wèn)題
在前面的例子中,利用 open 函數(shù)來(lái)聲明文件對(duì)象時(shí),省略了第三個(gè)參數(shù),即省略了編碼的指定。接下來(lái)介紹指定編碼后會(huì)出現(xiàn)什么問(wèn)題,以及該如何處理。
首先,打開文件“D:\python\PythonEXample\data.txt”,另存為“D:\python\PythonEXample\data1.txt”,但在保存時(shí)選擇編碼方式為“UTF-8”。
對(duì)“D:\python\PythonEXample\data1.txt”文件進(jìn)行操作:

結(jié)果顯示在解碼的時(shí)候遇到一些問(wèn)題,原因是之前我們保存的“data.txt”的編碼方式是“gbk”,而“data1.txt”的編碼方式是“UTF-8”,兩者的編碼方式不兼容。
這時(shí)可以通過(guò)在使用open函數(shù)時(shí)明確指明它的第三個(gè)參數(shù)來(lái)解決這個(gè)問(wèn)題。

因此,今后凡是出現(xiàn)UnicodeDecodeError的錯(cuò)誤(編碼錯(cuò)誤),首先想到的就是在打開文件時(shí)指定的編碼方式和保存文件時(shí)指定的編碼方式不兼容。此時(shí)只需要通過(guò)open函數(shù)中的第三個(gè)參數(shù)指定正確的編碼方式就可以了。
注意:文件打開時(shí)如果省略第三個(gè)參數(shù),則默認(rèn)以“gbk”的編碼方式打開,因此,如果保存文件時(shí)不是以“gbk”的方式保存的,請(qǐng)?jiān)诖蜷_文件時(shí)一定記得帶上參數(shù)“encoding”來(lái)指定編碼方式。如果文件是以“Unicode”方式保存的,則打開文件時(shí)指定的編碼參數(shù)為“encoding="UTF-16"”。
根據(jù)以上對(duì)全局函數(shù)open的操作,對(duì)于open("路徑","模式",encoding=''編碼"),總結(jié)如下:
(1)第二個(gè)參數(shù)、第三個(gè)參數(shù)都可以省略。
(2)省略第二個(gè)參數(shù),默認(rèn)以'r'模式打開。
(3)省略第三個(gè)參數(shù),默認(rèn)以“gbk”的編碼方式打開。
(4)打開文件時(shí)指定的編碼方式一定要和保存文件時(shí)的編碼方式一致。
3.1.3 文件寫入操作
前面主要介紹了Python對(duì)文本文件的讀取操作,下面介紹如何對(duì)文件進(jìn)行寫入操作。
現(xiàn)在使用 Python 來(lái)創(chuàng)建文本文件。假定要操作文件的位置還是在“D:\python\PythonEXample”目錄下,為了避免在接下來(lái)的操作中寫完整的路徑,我們把當(dāng)前的工作目錄切換到“D:\python\PythonEXample”目錄下,可以通過(guò)導(dǎo)入 os 模塊來(lái)實(shí)現(xiàn)。
1.write()方法
假設(shè)我們希望在“D:\python\PythonEXample”目錄下創(chuàng)建一個(gè)文件,保存一些信息,如寫入一個(gè)特定的字符串信息,則可以通過(guò)調(diào)用文件對(duì)象的“write()”方法來(lái)完成(其參數(shù)必須是字符串?。W⒁猓捍藭r(shí)若想換行,必須明確指定一個(gè)換行符“\n”來(lái)進(jìn)行換行,代碼如下:

執(zhí)行上述代碼后,到指定目錄下可以看到的確有一個(gè)“course.txt”文件,如圖 3-2所示。但雙擊打開該文件,發(fā)現(xiàn)文件里沒(méi)有任何文字內(nèi)容。
注意:open 函數(shù)的'w'模式只能創(chuàng)建文件,不能創(chuàng)建文件夾。如果要?jiǎng)?chuàng)建文件夾,請(qǐng)調(diào)用“os.mkdir(path)”或“os.makedirs(path)”來(lái)完成。

圖3-2 查看建立的文件
2.close()方法
上面用 write()創(chuàng)建的文件之所以沒(méi)有內(nèi)容,原因在于剛才的代碼還沒(méi)有編寫結(jié)束,其實(shí)剛才的操作還只是在內(nèi)存里的操作。如何才能將剛才寫入的內(nèi)容“單位:重慶師范大學(xué)\n”真實(shí)地反映在具體的文件里呢?方法是關(guān)閉剛才操作的文件(關(guān)閉連接)。完整的代碼如下:

此時(shí)再重新打開文件“course.txt”,就可以看到剛才寫入的內(nèi)容了。
打開文件后,可觀察到光標(biāo)所在位置是在寫入內(nèi)容的下一行,請(qǐng)問(wèn)為什么?
打開文件后,通過(guò)“另存為”對(duì)話框?qū)?huì)看到,其編碼方式的確為“UTF-8”。
3.writelines()方法、flush()方法
如果要一次寫入多行文本,可以事先將多行文本放到一個(gè)列表里,然后調(diào)用writelines()方法,它可以一次性地將列表中所有的信息寫入文本文件中。
同樣,代碼還在內(nèi)存里,它并沒(méi)有直接映射輸出到文件中。此時(shí)如果我們不想關(guān)閉文件,而又想將緩存的內(nèi)容映射到硬盤上,則可使用方法flush()來(lái)達(dá)成目標(biāo)。

注意:沒(méi)有“writeline()”方法!
當(dāng)打開所創(chuàng)建的文件后又發(fā)現(xiàn)新的問(wèn)題,本來(lái)希望把每一個(gè)姓名寫入文件的每一行中,但是發(fā)現(xiàn)所有的內(nèi)容都寫在一行上了,顯示結(jié)果如下:

此時(shí)只需要修改“names”變量,在其每個(gè)元素中加上一個(gè)換行符“\n”即可。

3.1.4 列表推導(dǎo)式
上面給出的加換行符的方法不推薦使用,因?yàn)槿绻?names 的元素個(gè)數(shù)很多,這樣的修改操作顯然不可取。下面介紹一種非常簡(jiǎn)便的方法——列表推導(dǎo)式。
1.列表推導(dǎo)式書寫形式
列表生成式(List Comprehensions),又叫列表推導(dǎo)式,是Python內(nèi)置的非常簡(jiǎn)單卻強(qiáng)大的可以用來(lái)創(chuàng)建列表的生成式[3],它是利用其他可迭代序列來(lái)創(chuàng)建新列表的一種方法。它的工作方式類似于for-in遍歷循環(huán)。其語(yǔ)法格式如下:

此處的“表達(dá)式”可以是有返回值的函數(shù)。
說(shuō)明:表達(dá)式中的變量來(lái)自 for-in 遍歷循環(huán)中的變量,隨著變量在迭代序列中的遍歷,將遍歷得到的值帶入表達(dá)式,表達(dá)式的值將作為列表中元素的值。
列表推導(dǎo)式的本質(zhì)是從可迭代序列中選出一部分或全部元素進(jìn)行運(yùn)算后作為新列表的元素,從而生成一個(gè)新的列表。注意,生成的是另外一個(gè)新列表,原列表保持不變。利用列表推導(dǎo)式能非常簡(jiǎn)潔地構(gòu)造一個(gè)新列表。
2.示例
【例3-6】 利用range函數(shù)生成一個(gè)由0~9每個(gè)數(shù)的平方作為元素的列表。

說(shuō)明:這里表達(dá)式“x*x”里的 x 來(lái)自 for-in 遍歷循環(huán)中的變量,而該變量 x 在“range(10)”產(chǎn)生的序列 0,1,2,…,9 中依次取值,并將每次遍歷的值代入表達(dá)式“x*x”進(jìn)行計(jì)算,然后將“x*x”的值作為最后得到的列表中的元素。所以,最后得到的列表為[0,1,4,9,16,25,36,49,64,81]。
如果希望得到的列表元素是能被 3 整除的數(shù)的平方,則在列表推導(dǎo)式中添加一個(gè)if表達(dá)式就可以完成。
【例 3-7】 利用 range 函數(shù)生成由 10 以內(nèi)且能被 3 整除的數(shù)的平方作為元素的列表。

第一次循環(huán)時(shí),變量x取0,此時(shí)條件0%3==0成立,因此,將此x的值帶入表達(dá)式“x*x”計(jì)算得到值 0,作為列表的第 1 個(gè)元素;第二次循環(huán)時(shí),變量 x 取 1,但此時(shí)條件 1%3==0 不成立,因此,不帶入表達(dá)式“x*x”進(jìn)行計(jì)算;第三次循環(huán)時(shí),變量 x 取 2,此時(shí),條件 2%3==0 仍然不成立,因此,也不帶入表達(dá)式“x*x”進(jìn)行計(jì)算;第四次循環(huán)時(shí),變量 x 取 3,條件 3%3==0 成立,因此,將其帶入表達(dá)式“x*x”計(jì)算得到值9,作為列表的第2個(gè)元素;……最后得到的列表為[0,9,36,81]。還可以增加更多的for語(yǔ)句來(lái)實(shí)現(xiàn)更為復(fù)雜的功能。
【例3-8】 利用range函數(shù)生成由數(shù)字0和1兩兩組合形成的列表作為元素的列表。

【例3-9】 利用range函數(shù)生成由數(shù)字0~2兩兩組合形成的元組作為元素的列表。

列表推導(dǎo)式總是返回一個(gè)列表。
【例 3-10】 遍歷元組(或列表)的每個(gè)元素,得到由元組(或列表)的每個(gè)元素的平方構(gòu)成的列表。

3.用列表推導(dǎo)式解決問(wèn)題
問(wèn)題:如3.1.3節(jié)最后提到的,我們希望把每一個(gè)姓名寫入文件的每一行中,而不是把所有的內(nèi)容都寫在同一行。
要解決以上問(wèn)題,可以重新聲明一個(gè)變量,但這里我們使用列表推導(dǎo)式來(lái)完成。新的列表 new_names 的元素等于之前的列表 names 中的元素 name 加上一個(gè)換行符“\n”,新的列表中的變量name來(lái)自之前的列表names中的元素。

這里,表達(dá)式為“name+'\n'”,表達(dá)式里的變量name來(lái)自for-in遍歷循環(huán)里的變量 name,而變量 name 在循環(huán)過(guò)程中會(huì)遍歷列表 names 中的所有元素,每遍歷出單個(gè)元素都把它當(dāng)作臨時(shí)變量name代入表達(dá)式“name+'\n'”中計(jì)算,得到的結(jié)果作為新列表里的一個(gè)元素,即在列表 names 中的每個(gè)元素后加上'\n',最終返回一個(gè)新的列表new_names。

此時(shí)再打開文件“people.txt”可看到結(jié)果如下:

思考:假設(shè)之前已經(jīng)在一行寫入了 4 個(gè)人名信息,現(xiàn)在再次寫入之后,為什么不是位于原有內(nèi)容之后,而是把原來(lái)的內(nèi)容替換掉了呢?請(qǐng)給出能得到正確結(jié)果的代碼。
提示:希望大家打開資源管理器窗口,仔細(xì)觀察文件操作過(guò)程中每個(gè)命令執(zhí)行時(shí)它的變化情況。
3.1.5 關(guān)閉文件
雖然通過(guò)調(diào)用方法 flush()能夠?qū)⒕彺娴膬?nèi)容寫到文件中,但這里再次強(qiáng)調(diào),最終文件的關(guān)閉還是要調(diào)用close()方法來(lái)完成。
在 Python 中,close()方法自動(dòng)進(jìn)行垃圾回收,釋放資源,所以,為了養(yǎng)成一個(gè)好的編程習(xí)慣,或者說(shuō)考慮到 Python 語(yǔ)言的不同實(shí)現(xiàn),應(yīng)該養(yǎng)成手動(dòng)關(guān)閉文件的習(xí)慣。但是寫代碼時(shí)往往容易忽略這個(gè)操作,此時(shí)可利用Python提供的上下文語(yǔ)法來(lái)實(shí)現(xiàn)[2]。
3.1.6 上下文語(yǔ)法
Python 中的上下文語(yǔ)法,具體來(lái)說(shuō)是通過(guò)一個(gè)特定的代碼段,將一系列的操作封裝在一個(gè)上下文的環(huán)境里(用關(guān)鍵字 with 進(jìn)行封裝),當(dāng)這個(gè)環(huán)境結(jié)束時(shí),它會(huì)自動(dòng)調(diào)用close()來(lái)關(guān)閉,而不需要我們手動(dòng)去調(diào)用close()了。
上下文語(yǔ)法的格式如下:

縮進(jìn)代碼體的操作都是圍繞對(duì)象f進(jìn)行的。
這樣就不用手動(dòng)調(diào)用f.close()來(lái)顯式地關(guān)閉文件了。在當(dāng)前的上下文代碼體執(zhí)行完畢后,當(dāng)前的資源會(huì)自動(dòng)釋放。
【例 3-11】 假定對(duì)某個(gè)文件要進(jìn)行讀取操作,而又不想顯式地調(diào)用 close()關(guān)閉文件,請(qǐng)利用上下文語(yǔ)法來(lái)實(shí)現(xiàn)。
如想讀取“people.txt”文件的內(nèi)容,通過(guò) open 函數(shù)打開一個(gè)文件,把它放到一個(gè)上下文對(duì)象f里,f不調(diào)用任何方法的時(shí)候其實(shí)是調(diào)用它本身的迭代對(duì)象,可以遍歷打印該迭代對(duì)象的所有內(nèi)容。打印完退出整個(gè)“with”上下文代碼體的時(shí)候,不用調(diào)用f.close(),系統(tǒng)會(huì)自動(dòng)關(guān)閉,并且釋放所需要的資源。代碼如下:

以上看到的是讀取操作,同樣,寫入操作也可類似地完成。

以上代碼并沒(méi)有包含 f.close()或 f.flush(),但是執(zhí)行上述代碼后,打開文件發(fā)現(xiàn)要寫入的信息已經(jīng)寫入文件“test.txt”中了,說(shuō)明執(zhí)行上下文代碼體后系統(tǒng)自動(dòng)調(diào)用了close()方法關(guān)閉文件。
實(shí)際開發(fā)時(shí)上下文語(yǔ)法比較實(shí)用,它可以避免顯式地調(diào)用 close()或編寫釋放資源的代碼。
3.1.7 生成器
通過(guò)列表推導(dǎo)式,我們可以直接創(chuàng)建一個(gè)列表。但是,受到內(nèi)存限制,列表容量肯定是有限的。創(chuàng)建一個(gè)規(guī)模很大的列表,不僅占用的存儲(chǔ)空間多,而且如果我們僅僅需要訪問(wèn)前面幾個(gè)元素,那后面絕大多數(shù)元素占用的空間都白白地浪費(fèi)了。
所以,如果列表元素可以按照某種算法推算出來(lái),那是否可以在循環(huán)的過(guò)程中不斷推算出后續(xù)的元素呢?如果可以這樣,就不必創(chuàng)建完整的列表,從而可以節(jié)省大量的空間。在Python中,這種一邊循環(huán)一邊計(jì)算的機(jī)制,稱為生成器(generator)。
1.創(chuàng)建生成器
要?jiǎng)?chuàng)建一個(gè)生成器,有很多種方法。這里介紹一種很簡(jiǎn)單的方法,只要把一個(gè)列表推導(dǎo)式的中括號(hào)[]改成小括號(hào)(),就創(chuàng)建了一個(gè)生成器。

說(shuō)明:只需要把創(chuàng)建列表推導(dǎo)式的中括號(hào)[]改成小括號(hào)()即得到了生成器,但注意,不是把創(chuàng)建列表的[]改成(),那樣得到的是一個(gè)元組。所以,要清楚列表的創(chuàng)建和列表推導(dǎo)式的創(chuàng)建是不同的,雖然最后的結(jié)果都是一個(gè)列表。
2.獲取生成器的每個(gè)元素
由于列表推導(dǎo)式最終的結(jié)果還是一個(gè)列表,因此,可以通過(guò)下標(biāo)索引的方式對(duì)列表中的元素進(jìn)行訪問(wèn)和截取。但對(duì)生成器中的元素,又該如何來(lái)訪問(wèn)呢?
由于生成器保存的是算法,因此,可以通過(guò)調(diào)用全局函數(shù) next()來(lái)獲得生成器的每個(gè)元素[4],直至計(jì)算到最后一個(gè)元素。若此時(shí)再次使用 next()來(lái)獲取元素,系統(tǒng)會(huì)拋出 StopIteration 異常。實(shí)際上,可以把生成器的數(shù)據(jù)流看作一個(gè)有序序列,雖然不知道序列的長(zhǎng)度,但是可以通過(guò)不斷地計(jì)算來(lái)獲取下一個(gè)值,直到最后拋出StopIteration異常。StopIteration異常用于標(biāo)識(shí)迭代的完成,防止出現(xiàn)無(wú)限循環(huán)的情況。
但要特別注意:一旦生成器的值用完了,再次調(diào)用 next()就會(huì)出現(xiàn)異常錯(cuò)誤,所以,每個(gè)生成器只能使用一次。
請(qǐng)仔細(xì)理解下面的每步操作。

更多next()函數(shù)的使用方法可以通過(guò)help(next)來(lái)了解。
通過(guò)next()函數(shù)雖然可以輸出生成器的每個(gè)元素,但獲取完最后一個(gè)元素后如果還試圖執(zhí)行 next()操作,系統(tǒng)會(huì)拋出 StopIteration 異常。所以,最好的方式是通過(guò) for-in遍歷循環(huán)來(lái)輸出生成器的每個(gè)元素,使用這種方式系統(tǒng)不會(huì)拋出異常。

也可以用下面的方式來(lái)達(dá)到同樣的目標(biāo):


生成器非常強(qiáng)大。當(dāng)推算的算法比較復(fù)雜,用類似列表推導(dǎo)式的 for-in 遍歷循環(huán)無(wú)法實(shí)現(xiàn)時(shí),還可以用函數(shù)來(lái)實(shí)現(xiàn)。更多有關(guān)生成器的內(nèi)容請(qǐng)大家查閱相關(guān)網(wǎng)站[3]或查看 Python 的官方文件[4]。生成器不僅可以使用 for-in 遍歷循環(huán)輸出每個(gè)元素,還可以通過(guò)不斷調(diào)用 next()函數(shù)返回下一個(gè)值,直到最后拋出 StopIteration 錯(cuò)誤,表示已經(jīng)讀取完生成器中的所有元素了。
- 大學(xué)計(jì)算機(jī)基礎(chǔ)(第二版)
- GraphQL學(xué)習(xí)指南
- Dynamics 365 Application Development
- Android 7編程入門經(jīng)典:使用Android Studio 2(第4版)
- 軟件項(xiàng)目管理實(shí)用教程
- C/C++數(shù)據(jù)結(jié)構(gòu)與算法速學(xué)速用大辭典
- Java EE企業(yè)級(jí)應(yīng)用開發(fā)教程(Spring+Spring MVC+MyBatis)
- Python機(jī)器學(xué)習(xí)之金融風(fēng)險(xiǎn)管理
- C++編程兵書
- Getting Started with Nano Server
- Visual FoxPro 6.0程序設(shè)計(jì)
- 區(qū)塊鏈架構(gòu)之美:從比特幣、以太坊、超級(jí)賬本看區(qū)塊鏈架構(gòu)設(shè)計(jì)
- Python機(jī)器學(xué)習(xí)與量化投資
- Android應(yīng)用程序設(shè)計(jì)
- Java Script從入門到精通(第5版)