Python使用os模塊實(shí)現(xiàn)更高效地讀寫文件
使用 os.open 打開文件
無論是讀文件還是寫文件,都要先打開文件。說到打開文件,估計(jì)首先想到的就是內(nèi)置函數(shù) open(即 io.open),那么它和 os.open 有什么關(guān)系呢?
內(nèi)置函數(shù) open 實(shí)際上是對(duì) os.open 的封裝,在 os.open 基礎(chǔ)上增加了相關(guān)訪問方法。因此為了操作方便,應(yīng)該調(diào)用內(nèi)置函數(shù) open 進(jìn)行文件操作,但如果對(duì)效率要求較高的話,則可以考慮使用 os.open。
此外 open 函數(shù)返回的是一個(gè)文件對(duì)象,我們可以在此基礎(chǔ)上進(jìn)行任意操作;而 os.open 返回的是一個(gè)文件描述符,說白了就是一個(gè)整數(shù),因?yàn)槊恳粋€(gè)文件對(duì)象都會(huì)對(duì)應(yīng)一個(gè)文件描述符。
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ù)實(shí)際上是封裝了 C 的 fopen,C 的 fopen 又封裝了系統(tǒng)調(diào)用提供的 open。

操作系統(tǒng)提供了很多的系統(tǒng)調(diào)用,打開文件則是 open,我們看到它返回一個(gè)整數(shù),這個(gè)整數(shù)就是對(duì)應(yīng)的文件描述符。C 的 fopen 封裝了系統(tǒng)調(diào)用的 open,返回的是一個(gè)文件指針。
所以內(nèi)置函數(shù) open 和 os.open 的區(qū)別就更加清晰了,內(nèi)置函數(shù) open 在底層會(huì)使用 C 的 fopen,得到的是一個(gè)封裝好的文件對(duì)象,在此基礎(chǔ)上可以直接操作。至于 os.open 在底層則不走 C 的 fopen,而是直接使用系統(tǒng)調(diào)用提供的 open,得到的是文件描述符。
os 模塊內(nèi)部的函數(shù)基本上都是直接走的系統(tǒng)調(diào)用,所以模塊名才叫 os。
然后我們使用 os.open 一般需要傳遞兩個(gè)參數(shù),第一個(gè)參數(shù)是文件名,第二個(gè)參數(shù)是模式,舉個(gè)栗子:
import?os
#?以只讀方式打開,要求文件必須存在
#?打開時(shí)光標(biāo)處于文件的起始位置
os.open("main.c",?os.O_RDONLY)
#?以只寫方式打開,要求文件必須存在
#?打開時(shí)光標(biāo)處于文件的起始位置
os.open("main.c",?os.O_WRONLY)
#?以可讀可寫方式打開,要求文件必須存在
#?打開時(shí)光標(biāo)處于文件的起始位置
os.open("main.c",?os.O_RDWR)
#?以只讀方式打開,文件不存在則創(chuàng)建
#?存在則不做任何事情,等價(jià)于?os.O_RDONLY
#?打開時(shí)光標(biāo)處于文件的起始位置
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)
#?文件不存在時(shí)創(chuàng)建,存在時(shí)清空
#?打開時(shí)光標(biāo)處于文件的起始位置
os.open("main.c",
????????os.O_WRONLY?|?os.O_CREAT?|?os.O_TRUNC)
#?當(dāng)然讀取文件也是可以的
#?比如?os.O_RDONLY?|?os.O_CREAT?|?os.O_TRUNC
#?也是文件存在時(shí)清空內(nèi)容,但是這沒有任何意義
#?因?yàn)樽x取的時(shí)候?qū)⑽募蹇樟耍沁€讀什么?
#?文件不存在時(shí)創(chuàng)建,存在時(shí)追加
#?打開時(shí)光標(biāo)處于文件的末尾
os.open("main.c",
????????os.O_WRONLY?|?os.O_CREAT?|?os.O_APPEND)
#?所以
"""
open里面的讀模式等價(jià)于這里的?os.O_RDONLY
open里面的寫模式等價(jià)于這里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_TRUNC
open里面的追加模式等價(jià)于這里的?os.O_WRONLY?|?os.O_CREATE?|?os.O_APPEND
"""好,打開方式介紹完了,那么怎么讀取和寫入呢?很簡(jiǎn)單,讀取使用 os.read,寫入使用 os.write。
使用 os.read 讀取文件
先來看讀取,os.read 接收兩個(gè)參數(shù),第一個(gè)參數(shù)是文件描述符,第二個(gè)參數(shù)是要讀取多少個(gè)字節(jié)。
import?os
fd?=?os.open("main.c",?os.O_RDONLY)
#?使用?os.read?進(jìn)行讀取
#?這里讀取?20?個(gè)字節(jié)
data?=?os.read(fd,?20)
print(data)
"""
b'#include?<Python.h>'
"""
#?再讀取?20?個(gè)字節(jié)
data?=?os.read(fd,?20)
print(data)
"""
b'\n#include?<ctype.h>'
"""
#?繼續(xù)讀取
data?=?os.read(fd,?20)
#?由于只剩下一個(gè)字節(jié)
#?所以就讀取了一個(gè)字節(jié)
#?顯然此時(shí)文件已經(jīng)讀完了
print(data)
"""
b'\n'
"""
#?文件讀取完畢之后
#?再讀取的話會(huì)返回空字節(jié)串
print(os.read(fd,?20))??#?b''
print(os.read(fd,?20))??#?b''
print(os.read(fd,?20))??#?b''所以這就是文件的讀取方式,還是很簡(jiǎn)單的。然后在讀取的過程中,我們還可以移動(dòng)光標(biāo),通過 os.lseek 函數(shù)。
- os.lseek(fd, m, 0):將光標(biāo)從文件的起始位置向后移動(dòng) m 個(gè)字節(jié);
- os.lseek(fd, m, 1):將光標(biāo)從當(dāng)前所在的位置向后移動(dòng) m 個(gè)字節(jié);
- os.lseek(fd, m, 2):將光標(biāo)從文件的結(jié)束位置向后移動(dòng) m 個(gè)字節(jié);
如果 m 大于 0,表示向后移動(dòng),m 小于 0,表示向前移動(dòng)。所以當(dāng)?shù)谌齻€(gè)參數(shù)為 2 的時(shí)候,也就是結(jié)束位置,那么 m 一般為負(fù)數(shù)。因?yàn)橄鄬?duì)于結(jié)束位置,肯定要向前移動(dòng),當(dāng)然向后移動(dòng)也可以,不過沒啥意義;同理當(dāng)?shù)谌齻€(gè)參數(shù)為 0 時(shí),m 一般為正數(shù),相對(duì)于起始位置,肯定要向后移動(dòng)。
import?os
fd?=?os.open("main.c",?os.O_RDONLY)
data?=?os.read(fd,?20)
print(data)
"""
b'#include?<Python.h>'
"""
#?從文件的起始位置向后移動(dòng)?0?個(gè)字節(jié)
#?相當(dāng)于將光標(biāo)設(shè)置在文件的起始位置
os.lseek(fd,?0,?0)
data?=?os.read(fd,?20)
print(data)
"""
b'#include?<Python.h>'
"""
#?設(shè)置在結(jié)束位置
os.lseek(fd,?0,?2)
print(os.read(fd,?20))??#?b''
#?此時(shí)就什么也讀不出來了然后我們提一下 stdin, stdout, stderr,含義應(yīng)該不需要解釋了,重點(diǎn)是它們對(duì)應(yīng)的文件描述符分別為 0, 1, 2。
import?os #?從標(biāo)準(zhǔn)輸入里面讀取?10?個(gè)字節(jié) #?沒錯(cuò),此時(shí)作用類似于?input while?True: ????data?=?os.read(0,?10).strip() ????print(f"你輸入了:",?data) ????if?data?==?b"exit": ????????break
我們測(cè)試一下:

os.read 可以實(shí)現(xiàn) input 的效果,并且效率更高。另外當(dāng)按下回車時(shí),換行符也會(huì)被讀進(jìn)去,所以需要 strip 一下。然后我們這里讀的是 10 個(gè)字節(jié),如果一次讀不完,那么會(huì)分多次讀取。在讀取文件的時(shí)候,也是同理。
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 結(jié)合,舉個(gè)栗子:
import?os
import?io
f?=?open("main.c",?"r")
#?通過?f.fileno()?即可拿到對(duì)應(yīng)的文件描述符
#?雖然這里是以文本模式打開的文件
#?但只要拿到文件描述符,都可以交給?os.read
print(
????os.read(f.fileno(),?10)
)??#?b'#include?<'
#?查看光標(biāo)位置
print(f.tell())??#?10
#?移動(dòng)光標(biāo)位置
#?從文件開頭向后移動(dòng)?5?字節(jié)
f.seek(5,?0)
print(f.tell())??#?5
#?os.lseek?也可以實(shí)現(xiàn)
os.lseek(f.fileno(),?3,?0)
print(f.tell())??#?3
#?此時(shí)會(huì)從第?4?個(gè)字節(jié)開始讀取
print(f.read())
"""
clude?<Python.h>
#include?<ctype.h>
"""
#?os.lseek?比?f.seek?要強(qiáng)大一些
#?移動(dòng)到文件末尾,此時(shí)沒問題
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
"""
#?但如果要相對(duì)文件末尾移動(dòng)具體的字節(jié)數(shù)
#?那么?f.seek?不支持,而?os.lseek?是可以的
print(f.tell())??#?41
os.lseek(f.fileno(),?-1,?2)
print(f.tell())??#?40
#?最后只剩下一個(gè)換行符
print(os.read(f.fileno(),?10))??#?b'\n'是不是很好玩呢?
使用 os.write 寫入文件
然后是寫入文件,調(diào)用 os.write 即可寫入。
import?os
#?此時(shí)可讀可寫,文件不存在時(shí)自動(dòng)創(chuàng)建,存在則清空
fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC)
#?寫入內(nèi)容,接收兩個(gè)參數(shù)
#?參數(shù)一:文件描述符;參數(shù)二:bytes 對(duì)象
os.write(fd,?b"hello,?")
os.write(fd,?"古明地覺".encode("utf-8"))
#?讀取內(nèi)容
data?=?os.read(fd,?1024)
print(data)??#?b''
#?問題來了,為啥讀取不到內(nèi)容呢?
#?很簡(jiǎn)單,因?yàn)楣鈽?biāo)會(huì)伴隨著數(shù)據(jù)的寫入而不斷后移
#?這樣的話,數(shù)據(jù)才能不斷地寫入
#?因此,現(xiàn)在的光標(biāo)位于文件的結(jié)尾處
#?想要查看寫入的內(nèi)容需要移動(dòng)到開頭
os.lseek(fd,?0,?0)
print(os.read(fd,?1024).decode("utf-8"))
"""
hello,?古明地覺
"""
#?從后往前移動(dòng)?3?字節(jié)
os.lseek(fd,?-3,?2)
print(os.read(fd,?1024).decode("utf-8"))
"""
覺
"""以上就是文件的寫入,當(dāng)然它也可以和內(nèi)置函數(shù) open 結(jié)合,通過 os.write(f.fileno(), b"xxx") 進(jìn)行寫入。但是不建議 os.open 和 open 混用,其實(shí)工作中使用 open 就足夠了。
然后是 stdout 和 stderr,和 os.write 結(jié)合可以實(shí)現(xiàn) print 的效果。
import?os
os.write(1,?"往?stdout?里面寫入\n".encode("utf-8"))
os.write(2,?"往?stderr?里面寫入\n".encode("utf-8"))執(zhí)行一下,查看控制臺(tái):

以上就是 os.write 的用法。
最后是關(guān)閉文件,使用 os.close 即可。
import?os
import?io
fd?=?os.open("1.txt",?os.O_RDWR?|?os.O_CREAT?|?os.O_TRUNC)
#?關(guān)閉文件
os.close(fd)
#?文件對(duì)象也是可以的
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() 關(guān)閉文件,再進(jìn)行讀取的話,會(huì)拋出一個(gè) ValueError,提示 I/O operation on closed file。這個(gè)報(bào)錯(cuò)信息比較明顯,不應(yīng)該在關(guān)閉的文件上執(zhí)行 IO 操作,因?yàn)槲募?duì)象知道文件已經(jīng)關(guān)閉了,畢竟調(diào)用的是自己的 close 方法,所以這個(gè)報(bào)錯(cuò)是解釋器給出的。當(dāng)然啦,調(diào)用 f.close 也會(huì)觸發(fā) os.close,因?yàn)殛P(guān)閉文件最終還是要交給操作系統(tǒng)負(fù)責(zé)的。
但如果是直接關(guān)閉底層的文件描述符,文件對(duì)象是不知道的,再使用 f.read() 依舊會(huì)觸發(fā)系統(tǒng)調(diào)用,也就是 os.read。而操作系統(tǒng)發(fā)現(xiàn)文件已經(jīng)關(guān)閉了,所以會(huì)報(bào)錯(cuò):文件描述符有問題,此時(shí)就是一個(gè) OSError,報(bào)錯(cuò)信息是操作系統(tǒng)給出的。
import?os f?=?open(r"1.txt",?"r") #?文件是否關(guān)閉 print(f.closed)??#?False os.close(f.fileno()) print(f.closed)??#?False #?所以調(diào)用?os.close,文件對(duì)象?f?并不知道 #?f.read?依舊會(huì)觸發(fā)系統(tǒng)調(diào)用
如果是使用 f.close()。
f?=?open(r"1.txt",?"r") f.close() print(f.closed)??#?True
后續(xù)執(zhí)行 IO 操作,就不會(huì)再走系統(tǒng)調(diào)用了,而是直接拋出 ValueError,原因是文件對(duì)象知道文件已經(jīng)關(guān)閉了。
除了 os.close 之外,還有一個(gè) os.closerange,可以關(guān)閉多個(gè)文件描述符對(duì)應(yīng)的文件。
import?os #?關(guān)閉文件描述符為?1、2、3、4?的文件? os.closerange(1,?5)
該方法不是很常用,了解一下即可。
以上就是使用 os 模塊操作文件,它是直接使用操作系統(tǒng)提供的系統(tǒng)調(diào)用,所以效率上會(huì)比內(nèi)置函數(shù) open 要高一些。但是工作中還是不太建議使用 os 模塊操作文件,使用內(nèi)置函數(shù) open 就好。
到此這篇關(guān)于Python使用os模塊實(shí)現(xiàn)更高效地讀寫文件的文章就介紹到這了,更多相關(guān)Python os模塊讀寫文件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python中快速進(jìn)行多個(gè)字符替換的方法小結(jié)
最近在用python給自己的seo工作提高效率和節(jié)省時(shí)間,發(fā)現(xiàn)python真的很不錯(cuò),可以完成很多事情。多個(gè)字符替換是大家可能都會(huì)遇到的一個(gè)問題,昨天在工作中就碰到了這么一個(gè)問題,所以想著記錄一下解決方案及其過程,方便以后參考。下面來一起看看吧。2016-12-12
python實(shí)現(xiàn)從ftp上下載文件的實(shí)例方法
在本篇文章里小編給大家整理了關(guān)于python實(shí)現(xiàn)從ftp上下載文件的實(shí)例方法,需要的朋友們可以參考下。2020-07-07
Python+threading模塊對(duì)單個(gè)接口進(jìn)行并發(fā)測(cè)試
這篇文章主要為大家詳細(xì)介紹了Python+threading模塊對(duì)單個(gè)接口進(jìn)行并發(fā)測(cè)試,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06
python 實(shí)現(xiàn)體質(zhì)指數(shù)BMI計(jì)算
這篇文章主要介紹了python 實(shí)現(xiàn)體質(zhì)指數(shù)BMI計(jì)算操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05
Python檢查判斷一個(gè)數(shù)是不是另一個(gè)數(shù)的整數(shù)次冪實(shí)例深究
在數(shù)學(xué)和計(jì)算中,確定一個(gè)數(shù)是否為另一個(gè)數(shù)的整數(shù)次冪是一個(gè)常見而重要的問題,例如,我們可能需要判斷一個(gè)數(shù)是否是某個(gè)數(shù)的平方、立方或其他冪次,本文將探討在Python中如何實(shí)現(xiàn)這一功能,通過數(shù)學(xué)方法和算法檢查一個(gè)數(shù)是否是另一個(gè)數(shù)的整數(shù)次冪2023-12-12
Python使用中文正則表達(dá)式匹配指定中文字符串的方法示例
這篇文章主要介紹了Python使用中文正則表達(dá)式匹配指定中文字符串的方法,結(jié)合實(shí)例形式分析了Python正則匹配及字符編碼相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
python實(shí)現(xiàn)有序遍歷dict(字典)
這篇文章主要介紹了python實(shí)現(xiàn)有序遍歷dict(字典),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08

