分析用Python腳本關(guān)閉文件操作的機(jī)制
如果不用“with”,那么Python會(huì)在何時(shí)關(guān)閉文件呢?答案是:視情況而定。
Python程序員最初學(xué)到的東西里有一點(diǎn)就是可以通過(guò)迭代法很容易地遍歷一個(gè)打開(kāi)文件的全文:
f = open('/etc/passwd') for line in f: print(line)
注意上面的代碼具有可行性,因?yàn)槲覀兊奈募?duì)象“f”是一個(gè)迭代器。換句話說(shuō),“f“ 知道在一個(gè)循環(huán)或者任何其他的迭代上下文中做什么,比如像列表解析。
我的Python課堂上的大多數(shù)學(xué)生都具有其他編程語(yǔ)言背景,在使用以前所熟悉的語(yǔ)言時(shí),他們總是在完成文件操作時(shí)被期望關(guān)閉文件。因此,在我向他們介紹了Python文件操作的內(nèi)容不久后他們問(wèn)起如何在Python中關(guān)閉文件時(shí),我一點(diǎn)都不驚訝。
最簡(jiǎn)單的回答就是我們可以通過(guò)調(diào)用f.close()顯式地關(guān)閉文件。一旦我們關(guān)閉了文件,該文件對(duì)象依然存在,但是我們無(wú)法再通過(guò)它來(lái)讀取文件內(nèi)容了,而且文件對(duì)象返回的可打印內(nèi)容也表明文件已經(jīng)被關(guān)閉。
>>> f = open('/etc/passwd') >>> f <open file '/etc/passwd', mode 'r' at 0x10f023270> >>> f.read(5) '##n# ' f.close() >>> f <closed file '/etc/passwd', mode 'r' at 0x10f023270> f.read(5) --------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-11-ef8add6ff846> in <module>() ----> 1 f.read(5) ValueError: I/O operation on closed file
所以是這樣,我在用Python編程的時(shí)候,很少明確地對(duì)文件調(diào)用 “close” 方法。此外,你也很可能不想或不必那樣做。
打開(kāi)文件的優(yōu)選最佳實(shí)踐方式是使用 “with” 語(yǔ)句,就像如下所示:
with open('/etc/passwd') as f: for line in f: print(line)
“with”語(yǔ)句對(duì) “f” 文件對(duì)象調(diào)用在Python中稱作“上下文管理器”的方法。也就是說(shuō),它指定 “f” 為指向 /etc/passwd 內(nèi)容的新的文件實(shí)例。在 “with” 打開(kāi)的代碼塊內(nèi),文件是打開(kāi)的,而且可以自由讀取。
然而,一旦Python代碼從 “with” 負(fù)責(zé)的代碼段退出,文件會(huì)自動(dòng)關(guān)閉。試圖在我們退出 “with”代碼塊后從 f 中讀取內(nèi)容會(huì)導(dǎo)致和上文一樣的 ValueError 異常。所以,通過(guò)使用 “with”,你避免了顯式地關(guān)閉文件的操作。Python 會(huì)以一種不那么有 Python 風(fēng)格的方式在幕后神奇而靜靜地替你關(guān)閉文件。
但是你不顯式地關(guān)閉文件會(huì)怎樣?如果你有點(diǎn)懶,既不使用 “with” 代碼塊也不調(diào)用f.close()怎么辦?這時(shí)文件會(huì)什么時(shí)候關(guān)閉?何時(shí)應(yīng)該關(guān)閉文件?
我之所以問(wèn)這個(gè),是因?yàn)槲医塘诉@么多年P(guān)ython,確信努力教授“with”或上下文管理器的同時(shí)又教很多其它的話題超出了學(xué)生接受的范圍。在介紹性課程談及 “with” 時(shí),我一般會(huì)告訴學(xué)生在他們職業(yè)生涯中遇到這個(gè)問(wèn)題時(shí),讓Python去關(guān)閉文件就好,不論文件對(duì)象的應(yīng)用計(jì)數(shù)降為0還是Python退出時(shí)。
在我的Python文件操作免費(fèi)e-mail課程中,我并沒(méi)有在所有的解決方案中使用with,想看看如何。結(jié)果一些人質(zhì)疑我,說(shuō)不使用“with”會(huì)向人們展示一種糟糕的實(shí)踐方案并且會(huì)有數(shù)據(jù)未寫入磁盤的風(fēng)險(xiǎn)。
我收到了很多關(guān)于此話題的郵件,于是我問(wèn)自己:如果我們沒(méi)有顯式地關(guān)閉文件或者沒(méi)用“with”代碼塊,那么Python會(huì)何時(shí)關(guān)閉文件?也就是說(shuō),如果我讓文件自動(dòng)關(guān)閉,那么會(huì)發(fā)生什么?
我總是假定當(dāng)對(duì)象的引用計(jì)數(shù)降為0時(shí),Python會(huì)關(guān)閉文件,進(jìn)而垃圾回收機(jī)制清理文件對(duì)象。當(dāng)我們讀文件時(shí)很難證明或核實(shí)這一點(diǎn),但寫入文件時(shí)卻很容易。這是因?yàn)楫?dāng)寫入文件時(shí),內(nèi)容并不會(huì)立即刷新到磁盤(除非你向“open”方法的第三個(gè)可選參數(shù)傳入“False”),只有當(dāng)文件關(guān)閉時(shí)才會(huì)刷新。
于是我決定做些實(shí)驗(yàn)以便更好地理解Python到底能自動(dòng)地為我做什么。我的實(shí)驗(yàn)包括打開(kāi)一個(gè)文件、寫入數(shù)據(jù)、刪除引用和退出Python。我很好奇數(shù)據(jù)是什么時(shí)候會(huì)被寫入,如果有的話。
我的實(shí)驗(yàn)是這個(gè)樣子:
f = open('/tmp/output', 'w') f.write('abcn') f.write('defn') # check contents of /tmp/output (1) del(f) # check contents of /tmp/output (2) # exit from Python # check contents of /tmp/output (3)
我在Mac平臺(tái)上用Python 2.7.9 做了第一個(gè)實(shí)驗(yàn),報(bào)告顯示在階段一文件存在但是是空的,階段二和階段三中文件包含所有的內(nèi)容。這樣,在CPython 2.7中我最初的直覺(jué)似乎是正確的:當(dāng)一個(gè)文件對(duì)象被垃圾回收時(shí),它的 __del__ (或者等價(jià)的)方法會(huì)刷新并關(guān)閉文件。而且在我的IPython進(jìn)程中調(diào)用“l(fā)sof”命令顯示文件確實(shí)在引用對(duì)象移除后被關(guān)閉了。
那 Python3 如何呢?我在Mac上 Python 3.4.2 環(huán)境下做了以上的實(shí)驗(yàn),得到了相同的結(jié)果。移除對(duì)文件對(duì)象最后的引用后會(huì)導(dǎo)致文件被刷新并且被關(guān)閉。
這對(duì)于 Python 2.7 和 3.4 很好。但是在 PyPy 和 Jython下的替代實(shí)現(xiàn)會(huì)怎樣呢?或許情況會(huì)有些不同。
于是我在 PyPy 2.7.8 下做了相同的實(shí)驗(yàn)。而這次,我得到了不同的結(jié)果!刪除文件對(duì)象的引用后——也就是在階段2,并沒(méi)有導(dǎo)致文件內(nèi)容被刷入磁盤。我不得不假設(shè)這和垃圾回收機(jī)制的不同或其他在 PyPy 和 CPython中工作機(jī)制的不同有關(guān)系。但是如果你在 PyPy中運(yùn)行程序,就絕不要指望僅僅因?yàn)槲募?duì)象的引用結(jié)束,文件就會(huì)被刷新和關(guān)閉。命令 lsof 顯示直到Python進(jìn)程退出時(shí)文件才會(huì)被釋放。
為了好玩,我決定嘗試一下 Jython 2.7b3. 結(jié)果Jython 表現(xiàn)出了和PyPy一樣的行為。也就是說(shuō),從 Python 退出確實(shí)會(huì)確保緩存中的數(shù)據(jù)寫入磁盤。
我重做了這些實(shí)驗(yàn),但是我把 “abcn”和 “defn”換成了 “abcn”*1000 和“defn”*1000.
在 Python 2.7 的環(huán)境下,“abcn” * 1000 語(yǔ)句執(zhí)行后沒(méi)有任何東西寫入。但“defn” * 1000 語(yǔ)句執(zhí)行后,文件包含有4096個(gè)字節(jié)——可能代表緩沖區(qū)的大小。調(diào)用 del(f) 刪除文件對(duì)象的引用導(dǎo)致數(shù)據(jù)被刷入磁盤和文件關(guān)閉,此時(shí)文件中共有8000字節(jié)的數(shù)據(jù)。所以忽略字符串大小的話 Python 2.7 的行為表現(xiàn)基本相同。唯一不同的是如果超出了緩沖區(qū)的大小,那么一些數(shù)據(jù)將在最后文件關(guān)閉數(shù)據(jù)刷新前寫入磁盤。
換做是Python 3的話,情況就有些不同了。f.write執(zhí)行后沒(méi)有任何數(shù)據(jù)會(huì)寫入。但是文件對(duì)象引用一旦結(jié)束,文件就會(huì)刷新并關(guān)閉。這可能是緩沖區(qū)很大的緣故。但毫無(wú)疑問(wèn),刪除文件對(duì)象引用會(huì)使文件刷新并關(guān)閉。
至于 PyPy 和 Jython,對(duì)大文件和小文件的操作結(jié)果都一樣:文件在 PyPy 或 Jython 進(jìn)程結(jié)束的時(shí)候刷新并關(guān)閉,而不是在文件對(duì)象的引用結(jié)束的時(shí)候。
為了再次確認(rèn),我又使用 “with” 進(jìn)行了實(shí)驗(yàn)。在所有情況下,我們都能夠輕松的預(yù)測(cè)文件是何時(shí)被刷新和關(guān)閉的——就是當(dāng)退出代碼段,并且上下文管理器在后臺(tái)調(diào)用合適方法的時(shí)候。
換句話說(shuō),如果你不使用“with”,那么至少在非常簡(jiǎn)單的情形下,你的數(shù)據(jù)不一定有丟失的危險(xiǎn)。然而你還是不能確定數(shù)據(jù)到底是在文件對(duì)象引用結(jié)束還是程序退出的時(shí)候被保存的。如果你假定因?yàn)閷?duì)文件唯一的引用是一個(gè)本地變量所以文件在函數(shù)返回時(shí)會(huì)關(guān)閉,那么事實(shí)一定會(huì)讓你感到吃驚。如果你有多個(gè)進(jìn)程或線程同時(shí)對(duì)一個(gè)文件進(jìn)行寫操作,那么你真的要非常小心了。
或許這個(gè)行為可以更好地定義不就可以在不同的平臺(tái)上表現(xiàn)得基本一致了嗎?也許我們甚至可以看看Python規(guī)范的開(kāi)始,而不是指著CPython說(shuō)“Yeah,不管版本如何總是對(duì)的”。
我依然覺(jué)得“with”和上下文管理器很棒。而且我想對(duì)于Python新手,理解“with”的工作原理很難。但我還是不得不提醒新手開(kāi)發(fā)者注意:如果他們決定使用Python的其他可選版本,那么會(huì)出現(xiàn)很多不同于CPython的古怪情況而且如果他們不夠小心,甚至?xí)钍芷浜Α?br />
- python smtplib模塊發(fā)送SSL/TLS安全郵件實(shí)例
- Python操作sqlite3快速、安全插入數(shù)據(jù)(防注入)的實(shí)例
- 詳解Python中open()函數(shù)指定文件打開(kāi)方式的用法
- Python文件右鍵找不到IDLE打開(kāi)項(xiàng)解決辦法
- python使用wxPython打開(kāi)并播放wav文件的方法
- python打開(kāi)文件并獲取文件相關(guān)屬性的方法
- python通過(guò)wxPython打開(kāi)一個(gè)音頻文件并播放的方法
- Python編程中用close()方法關(guān)閉文件的教程
- Python簡(jiǎn)單實(shí)現(xiàn)安全開(kāi)關(guān)文件的兩種方式
相關(guān)文章
Python函數(shù)基礎(chǔ)(定義函數(shù)、函數(shù)參數(shù)、匿名函數(shù))
這篇文章介紹了Python函數(shù)的基礎(chǔ)用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05python爬蟲(chóng)lxml庫(kù)解析xpath網(wǎng)頁(yè)過(guò)程示例
這篇文章主要為大家介紹了python爬蟲(chóng)lxml庫(kù)解析xpath網(wǎng)頁(yè)的過(guò)程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05使用python處理題庫(kù)表格并轉(zhuǎn)化為word形式的實(shí)現(xiàn)
這篇文章主要介紹了使用python處理題庫(kù)表格并轉(zhuǎn)化為word形式的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04使用WingPro 7 設(shè)置Python路徑的方法
Python使用稱為Python Path的搜索路徑來(lái)查找使用import語(yǔ)句導(dǎo)入代碼的模塊。這篇文章主要介紹了使用WingPro 7 設(shè)置Python路徑的方法,需要的朋友可以參考下2019-07-07Boston數(shù)據(jù)集預(yù)測(cè)放假及應(yīng)用優(yōu)缺點(diǎn)評(píng)估
這篇文章主要為大家介紹了Boston數(shù)據(jù)集預(yù)測(cè)放假及應(yīng)用優(yōu)缺點(diǎn)評(píng)估,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10python 實(shí)現(xiàn)網(wǎng)易郵箱郵件閱讀和刪除的輔助小腳本
這篇文章主要介紹了python 如何實(shí)現(xiàn)網(wǎng)易郵箱郵件閱讀和刪除輔助的小腳本,幫助大家更好的理解和學(xué)習(xí)使用python,感興趣的朋友可以了解下2021-03-03使用Python的內(nèi)建模塊collections的教程
這篇文章主要介紹了使用Python的內(nèi)建模塊collections的教程,示例代碼基于Python2.x版本,需要的朋友可以參考下2015-04-04python中實(shí)現(xiàn)精確的浮點(diǎn)數(shù)運(yùn)算詳解
計(jì)算機(jī)智能處理可數(shù)集合的運(yùn)算,但是全體實(shí)數(shù)是不可數(shù)的,所以計(jì)算機(jī)只能用一些奇怪的方法來(lái)擬合他,于是就產(chǎn)生了浮點(diǎn)數(shù)。下面這篇文章主要給大家介紹了關(guān)于python中實(shí)現(xiàn)精確浮點(diǎn)數(shù)運(yùn)算的相關(guān)資料,需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-11-11