Python使用os模塊實現(xiàn)更高效地讀寫文件
使用 os.open 打開文件
無論是讀文件還是寫文件,都要先打開文件。說到打開文件,估計首先想到的就是內(nèi)置函數(shù) open(即 io.open),那么它和 os.open 有什么關系呢?
內(nèi)置函數(shù) open 實際上是對 os.open 的封裝,在 os.open 基礎上增加了相關訪問方法。因此為了操作方便,應該調(diào)用內(nèi)置函數(shù) open 進行文件操作,但如果對效率要求較高的話,則可以考慮使用 os.open。
此外 open 函數(shù)返回的是一個文件對象,我們可以在此基礎上進行任意操作;而 os.open 返回的是一個文件描述符,說白了就是一個整數(shù),因為每一個文件對象都會對應一個文件描述符。
import?os f1?=?open("main.c",?"r") f2?=?os.open("main.c",?os.O_RDONLY) print(f1.__class__) print(f2.__class__) """ <class?'_io.TextIOWrapper'> <class?'int'> """
Python 的 open 函數(shù)實際上是封裝了 C 的 fopen,C 的 fopen 又封裝了系統(tǒng)調(diào)用提供的 open。
操作系統(tǒng)提供了很多的系統(tǒng)調(diào)用,打開文件則是 open,我們看到它返回一個整數(shù),這個整數(shù)就是對應的文件描述符。C 的 fopen 封裝了系統(tǒng)調(diào)用的 open,返回的是一個文件指針。
所以內(nèi)置函數(shù) open 和 os.open 的區(qū)別就更加清晰了,內(nèi)置函數(shù) open 在底層會使用 C 的 fopen,得到的是一個封裝好的文件對象,在此基礎上可以直接操作。至于 os.open 在底層則不走 C 的 fopen,而是直接使用系統(tǒng)調(diào)用提供的 open,得到的是文件描述符。
os 模塊內(nèi)部的函數(shù)基本上都是直接走的系統(tǒng)調(diào)用,所以模塊名才叫 os。
然后我們使用 os.open 一般需要傳遞兩個參數(shù),第一個參數(shù)是文件名,第二個參數(shù)是模式,舉個栗子:
import?os #?以只讀方式打開,要求文件必須存在 #?打開時光標處于文件的起始位置 os.open("main.c",?os.O_RDONLY) #?以只寫方式打開,要求文件必須存在 #?打開時光標處于文件的起始位置 os.open("main.c",?os.O_WRONLY) #?以可讀可寫方式打開,要求文件必須存在 #?打開時光標處于文件的起始位置 os.open("main.c",?os.O_RDWR) #?以只讀方式打開,文件不存在則創(chuàng)建 #?存在則不做任何事情,等價于?os.O_RDONLY #?打開時光標處于文件的起始位置 os.open("main.c",?os.O_RDONLY?|?os.O_CREAT) #?同理?os.O_WRONLY?和?os.O_RDWR?與之類似 os.open("main.c",?os.O_WRONLY?|?os.O_CREAT) os.open("main.c",?os.O_RDWR?|?os.O_CREAT) #?文件不存在時創(chuàng)建,存在時清空 #?打開時光標處于文件的起始位置 os.open("main.c", ????????os.O_WRONLY?|?os.O_CREAT?|?os.O_TRUNC) #?當然讀取文件也是可以的 #?比如?os.O_RDONLY?|?os.O_CREAT?|?os.O_TRUNC #?也是文件存在時清空內(nèi)容,但是這沒有任何意義 #?因為讀取的時候?qū)⑽募蹇樟?,那還讀什么? #?文件不存在時創(chuàng)建,存在時追加 #?打開時光標處于文件的末尾 os.open("main.c", ????????os.O_WRONLY?|?os.O_CREAT?|?os.O_APPEND) #?所以 """ open里面的讀模式等價于這里的?os.O_RDONLY open里面的寫模式等價于這里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_TRUNC open里面的追加模式等價于這里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_APPEND """
好,打開方式介紹完了,那么怎么讀取和寫入呢?很簡單,讀取使用 os.read,寫入使用 os.write。
使用 os.read 讀取文件
先來看讀取,os.read 接收兩個參數(shù),第一個參數(shù)是文件描述符,第二個參數(shù)是要讀取多少個字節(jié)。
import?os fd?=?os.open("main.c",?os.O_RDONLY) #?使用?os.read?進行讀取 #?這里讀取?20?個字節(jié) data?=?os.read(fd,?20) print(data) """ b'#include?<Python.h>' """ #?再讀取?20?個字節(jié) data?=?os.read(fd,?20) print(data) """ b'\n#include?<ctype.h>' """ #?繼續(xù)讀取 data?=?os.read(fd,?20) #?由于只剩下一個字節(jié) #?所以就讀取了一個字節(jié) #?顯然此時文件已經(jīng)讀完了 print(data) """ b'\n' """ #?文件讀取完畢之后 #?再讀取的話會返回空字節(jié)串 print(os.read(fd,?20))??#?b'' print(os.read(fd,?20))??#?b'' print(os.read(fd,?20))??#?b''
所以這就是文件的讀取方式,還是很簡單的。然后在讀取的過程中,我們還可以移動光標,通過 os.lseek 函數(shù)。
- os.lseek(fd, m, 0):將光標從文件的起始位置向后移動 m 個字節(jié);
- os.lseek(fd, m, 1):將光標從當前所在的位置向后移動 m 個字節(jié);
- os.lseek(fd, m, 2):將光標從文件的結束位置向后移動 m 個字節(jié);
如果 m 大于 0,表示向后移動,m 小于 0,表示向前移動。所以當?shù)谌齻€參數(shù)為 2 的時候,也就是結束位置,那么 m 一般為負數(shù)。因為相對于結束位置,肯定要向前移動,當然向后移動也可以,不過沒啥意義;同理當?shù)谌齻€參數(shù)為 0 時,m 一般為正數(shù),相對于起始位置,肯定要向后移動。
import?os fd?=?os.open("main.c",?os.O_RDONLY) data?=?os.read(fd,?20) print(data) """ b'#include?<Python.h>' """ #?從文件的起始位置向后移動?0?個字節(jié) #?相當于將光標設置在文件的起始位置 os.lseek(fd,?0,?0) data?=?os.read(fd,?20) print(data) """ b'#include?<Python.h>' """ #?設置在結束位置 os.lseek(fd,?0,?2) print(os.read(fd,?20))??#?b'' #?此時就什么也讀不出來了
然后我們提一下 stdin, stdout, stderr,含義應該不需要解釋了,重點是它們對應的文件描述符分別為 0, 1, 2。
import?os #?從標準輸入里面讀取?10?個字節(jié) #?沒錯,此時作用類似于?input while?True: ????data?=?os.read(0,?10).strip() ????print(f"你輸入了:",?data) ????if?data?==?b"exit": ????????break
我們測試一下:
os.read 可以實現(xiàn) input 的效果,并且效率更高。另外當按下回車時,換行符也會被讀進去,所以需要 strip 一下。然后我們這里讀的是 10 個字節(jié),如果一次讀不完,那么會分多次讀取。在讀取文件的時候,也是同理。
from?io?import?BytesIO import?os fd?=?os.open("main.c",?os.O_RDONLY) buf?=?BytesIO() while?True: ????data?=?os.read(fd,?10) ????if?data?!=?b"": ????????buf.write(data) ????else: ????????break print(buf.getvalue().decode("utf-8")) """ #include?<Python.h> #include?<ctype.h> """
然后 os.read 還可以和內(nèi)置函數(shù) open 結合,舉個栗子:
import?os import?io f?=?open("main.c",?"r") #?通過?f.fileno()?即可拿到對應的文件描述符 #?雖然這里是以文本模式打開的文件 #?但只要拿到文件描述符,都可以交給?os.read print( ????os.read(f.fileno(),?10) )??#?b'#include?<' #?查看光標位置 print(f.tell())??#?10 #?移動光標位置 #?從文件開頭向后移動?5?字節(jié) f.seek(5,?0) print(f.tell())??#?5 #?os.lseek?也可以實現(xiàn) os.lseek(f.fileno(),?3,?0) print(f.tell())??#?3 #?此時會從第?4?個字節(jié)開始讀取 print(f.read()) """ clude?<Python.h> #include?<ctype.h> """ #?os.lseek?比?f.seek?要強大一些 #?移動到文件末尾,此時沒問題 f.seek(0,?2) print(f.tell())??#?41 try: ????f.seek(-1,?2) except?io.UnsupportedOperation?as?e: ????print(e) """ can't?do?nonzero?end-relative?seeks """ #?但如果要相對文件末尾移動具體的字節(jié)數(shù) #?那么?f.seek?不支持,而?os.lseek?是可以的 print(f.tell())??#?41 os.lseek(f.fileno(),?-1,?2) print(f.tell())??#?40 #?最后只剩下一個換行符 print(os.read(f.fileno(),?10))??#?b'\n'
是不是很好玩呢?
使用 os.write 寫入文件
然后是寫入文件,調(diào)用 os.write 即可寫入。
import?os #?此時可讀可寫,文件不存在時自動創(chuàng)建,存在則清空 fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC) #?寫入內(nèi)容,接收兩個參數(shù) #?參數(shù)一:文件描述符;參數(shù)二:bytes 對象 os.write(fd,?b"hello,?") os.write(fd,?"古明地覺".encode("utf-8")) #?讀取內(nèi)容 data?=?os.read(fd,?1024) print(data)??#?b'' #?問題來了,為啥讀取不到內(nèi)容呢? #?很簡單,因為光標會伴隨著數(shù)據(jù)的寫入而不斷后移 #?這樣的話,數(shù)據(jù)才能不斷地寫入 #?因此,現(xiàn)在的光標位于文件的結尾處 #?想要查看寫入的內(nèi)容需要移動到開頭 os.lseek(fd,?0,?0) print(os.read(fd,?1024).decode("utf-8")) """ hello,?古明地覺 """ #?從后往前移動?3?字節(jié) os.lseek(fd,?-3,?2) print(os.read(fd,?1024).decode("utf-8")) """ 覺 """
以上就是文件的寫入,當然它也可以和內(nèi)置函數(shù) open 結合,通過 os.write(f.fileno(), b"xxx") 進行寫入。但是不建議 os.open 和 open 混用,其實工作中使用 open 就足夠了。
然后是 stdout 和 stderr,和 os.write 結合可以實現(xiàn) print 的效果。
import?os os.write(1,?"往?stdout?里面寫入\n".encode("utf-8")) os.write(2,?"往?stderr?里面寫入\n".encode("utf-8"))
執(zhí)行一下,查看控制臺:
以上就是 os.write 的用法。
最后是關閉文件,使用 os.close 即可。
import?os import?io fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC) #?關閉文件 os.close(fd) #?文件對象也是可以的 f?=?open(r"1.txt",?"r") os.close(f.fileno()) try: ????f.read() except?OSError?as?e: ????print(e) """ [Errno?9]?Bad?file?descriptor """
如果是調(diào)用 f.close() 關閉文件,再進行讀取的話,會拋出一個 ValueError,提示 I/O operation on closed file。這個報錯信息比較明顯,不應該在關閉的文件上執(zhí)行 IO 操作,因為文件對象知道文件已經(jīng)關閉了,畢竟調(diào)用的是自己的 close 方法,所以這個報錯是解釋器給出的。當然啦,調(diào)用 f.close 也會觸發(fā) os.close,因為關閉文件最終還是要交給操作系統(tǒng)負責的。
但如果是直接關閉底層的文件描述符,文件對象是不知道的,再使用 f.read() 依舊會觸發(fā)系統(tǒng)調(diào)用,也就是 os.read。而操作系統(tǒng)發(fā)現(xiàn)文件已經(jīng)關閉了,所以會報錯:文件描述符有問題,此時就是一個 OSError,報錯信息是操作系統(tǒng)給出的。
import?os f?=?open(r"1.txt",?"r") #?文件是否關閉 print(f.closed)??#?False os.close(f.fileno()) print(f.closed)??#?False #?所以調(diào)用?os.close,文件對象?f?并不知道 #?f.read?依舊會觸發(fā)系統(tǒng)調(diào)用
如果是使用 f.close()。
f?=?open(r"1.txt",?"r") f.close() print(f.closed)??#?True
后續(xù)執(zhí)行 IO 操作,就不會再走系統(tǒng)調(diào)用了,而是直接拋出 ValueError,原因是文件對象知道文件已經(jīng)關閉了。
除了 os.close 之外,還有一個 os.closerange,可以關閉多個文件描述符對應的文件。
import?os #?關閉文件描述符為?1、2、3、4?的文件? os.closerange(1,?5)
該方法不是很常用,了解一下即可。
以上就是使用 os 模塊操作文件,它是直接使用操作系統(tǒng)提供的系統(tǒng)調(diào)用,所以效率上會比內(nèi)置函數(shù) open 要高一些。但是工作中還是不太建議使用 os 模塊操作文件,使用內(nèi)置函數(shù) open 就好。
到此這篇關于Python使用os模塊實現(xiàn)更高效地讀寫文件的文章就介紹到這了,更多相關Python os模塊讀寫文件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python+threading模塊對單個接口進行并發(fā)測試
這篇文章主要為大家詳細介紹了Python+threading模塊對單個接口進行并發(fā)測試,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-06-06python 實現(xiàn)體質(zhì)指數(shù)BMI計算
這篇文章主要介紹了python 實現(xiàn)體質(zhì)指數(shù)BMI計算操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-05-05Python檢查判斷一個數(shù)是不是另一個數(shù)的整數(shù)次冪實例深究
在數(shù)學和計算中,確定一個數(shù)是否為另一個數(shù)的整數(shù)次冪是一個常見而重要的問題,例如,我們可能需要判斷一個數(shù)是否是某個數(shù)的平方、立方或其他冪次,本文將探討在Python中如何實現(xiàn)這一功能,通過數(shù)學方法和算法檢查一個數(shù)是否是另一個數(shù)的整數(shù)次冪2023-12-12