無(wú)論是讀文件還是寫(xiě)文件,都要先打開(kāi)文件。說(shuō)到打開(kāi)文件,估計(jì)首先想到的就是內(nèi)置函數(shù) open(即 io.open),那么它和 os.open 有什么關(guān)系呢?
內(nèi)置函數(shù) open 實(shí)際上是對(duì) os.open 的封裝,在 os.open 基礎(chǔ)上增加了相關(guān)訪問(wèn)方法。因此為了操作方便,應(yīng)該調(diào)用內(nèi)置函數(shù) open 進(jìn)行文件操作,但如果對(duì)效率要求較高的話,則可以考慮使用 os.open。 此外 open 函數(shù)返回的是一個(gè)文件對(duì)象,我們可以在此基礎(chǔ)上進(jìn)行任意操作;而 os.open 返回的是一個(gè)文件描述符,說(shuō)白了就是一個(gè)整數(shù),因?yàn)?span style="color: rgb(51, 51, 51);font-family: mp-quote, -apple-system-font, BlinkMacSystemFont, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 15px;font-style: normal;font-variant-ligatures: normal;font-variant-caps: normal;font-weight: 400;letter-spacing: normal;orphans: 2;text-align: justify;text-indent: 0px;text-transform: none;widows: 2;word-spacing: 0px;-webkit-text-stroke-width: 0px;text-decoration-thickness: initial;text-decoration-style: initial;text-decoration-color: initial;display: inline !important;float: none;">每一個(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)用,打開(kāi)文件則是 open,我們看到它返回一個(gè)整數(shù),這個(gè)整數(shù)就是對(duì)應(yīng)的文件描述符。C 的 fopen 封裝了系統(tǒng)調(diào)用的 open,返回的是一個(gè)文件指針。
所以?xún)?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
# 以只讀方式打開(kāi),要求文件必須存在 # 打開(kāi)時(shí)光標(biāo)處于文件的起始位置 os.open("main.c", os.O_RDONLY)
# 以只寫(xiě)方式打開(kāi),要求文件必須存在 # 打開(kāi)時(shí)光標(biāo)處于文件的起始位置 os.open("main.c", os.O_WRONLY)
# 以可讀可寫(xiě)方式打開(kāi),要求文件必須存在 # 打開(kāi)時(shí)光標(biāo)處于文件的起始位置 os.open("main.c", os.O_RDWR)
# 以只讀方式打開(kāi),文件不存在則創(chuàng)建 # 存在則不做任何事情,等價(jià)于 os.O_RDONLY # 打開(kāi)時(shí)光標(biāo)處于文件的起始位置 os.open("main.c", os.O_RDONLY | os.O_CREAT)
# 同理 os.O_WRONLY 和 os.O_RDWR 與之類(lèi)似 os.open("main.c", os.O_WRONLY | os.O_CREAT) os.open("main.c", os.O_RDWR | os.O_CREAT)
# 文件不存在時(shí)創(chuàng)建,存在時(shí)清空 # 打開(kāi)時(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)容,但是這沒(méi)有任何意義 # 因?yàn)樽x取的時(shí)候?qū)⑽募蹇樟耍沁€讀什么?
# 文件不存在時(shí)創(chuàng)建,存在時(shí)追加 # 打開(kāi)時(shí)光標(biāo)處于文件的末尾 os.open("main.c", os.O_WRONLY | os.O_CREAT | os.O_APPEND)
# 所以 """ open里面的讀模式等價(jià)于這里的 os.O_RDONLY open里面的寫(xiě)模式等價(jià)于這里的 os.O_WRONLY | os.O_CREATE | os.O_TRUNC open里面的追加模式等價(jià)于這里的 os.O_WRONLY | os.O_CREATE | os.O_APPEND """
好,打開(kāi)方式介紹完了,那么怎么讀取和寫(xiě)入呢?很簡(jiǎn)單,讀取使用 os.read,寫(xiě)入使用 os.write。 先來(lái)看讀取,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)單的。然后在讀取的過(guò)程中,我們還可以移動(dòng)光標(biāo),通過(guò) 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)也可以,不過(guò)沒(méi)啥意義;同理當(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í)就什么也讀不出來(lái)了
然后我們提一下 stdin, stdout, stderr,含義應(yīng)該不需要解釋了,重點(diǎn)是它們對(duì)應(yīng)的文件描述符分別為 0, 1, 2。 import os
# 從標(biāo)準(zhǔn)輸入里面讀取 10 個(gè)字節(jié) # 沒(méi)錯(cuò),此時(shí)作用類(lèi)似于 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)按下回車(chē)時(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") # 通過(guò) f.fileno() 即可拿到對(duì)應(yīng)的文件描述符 # 雖然這里是以文本模式打開(kāi)的文件 # 但只要拿到文件描述符,都可以交給 os.read print( os.read(f.fileno(), 10) ) # b'#include <'
# 查看光標(biāo)位置 print(f.tell()) # 10
# 移動(dòng)光標(biāo)位置 # 從文件開(kāi)頭向后移動(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é)開(kāi)始讀取 print(f.read()) """ clude <Python.h> #include <ctype.h>
"""
# os.lseek 比 f.seek 要強(qiáng)大一些 # 移動(dòng)到文件末尾,此時(shí)沒(méi)問(wèn)題 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'
是不是很好玩呢?
然后是寫(xiě)入文件,調(diào)用 os.write 即可寫(xiě)入。 import os
# 此時(shí)可讀可寫(xiě),文件不存在時(shí)自動(dòng)創(chuàng)建,存在則清空 fd = os.open("1.txt", os.O_RDWR | os.O_CREAT | os.O_TRUNC) # 寫(xiě)入內(nèi)容,接收兩個(gè)參數(shù) # 參數(shù)一:文件描述符;參數(shù)二:bytes 對(duì)象 os.write(fd, b"hello, ") os.write(fd, "古明地覺(jué)".encode("utf-8")) # 讀取內(nèi)容 data = os.read(fd, 1024) print(data) # b'' # 問(wèn)題來(lái)了,為啥讀取不到內(nèi)容呢? # 很簡(jiǎn)單,因?yàn)楣鈽?biāo)會(huì)伴隨著數(shù)據(jù)的寫(xiě)入而不斷后移 # 這樣的話,數(shù)據(jù)才能不斷地寫(xiě)入 # 因此,現(xiàn)在的光標(biāo)位于文件的結(jié)尾處 # 想要查看寫(xiě)入的內(nèi)容需要移動(dòng)到開(kāi)頭 os.lseek(fd, 0, 0) print(os.read(fd, 1024).decode("utf-8")) """ hello, 古明地覺(jué) """ # 從后往前移動(dòng) 3 字節(jié) os.lseek(fd, -3, 2) print(os.read(fd, 1024).decode("utf-8")) """ 覺(jué) """
以上就是文件的寫(xiě)入,當(dāng)然它也可以和內(nèi)置函數(shù) open 結(jié)合,通過(guò) os.write(f.fileno(), b"xxx") 進(jìn)行寫(xiě)入。但是不建議 os.open 和 open 混用,其實(shí)工作中使用 open 就足夠了。
然后是 stdout 和 stderr,和 os.write 結(jié)合可以實(shí)現(xiàn) print 的效果。 import os
os.write(1, "往 stdout 里面寫(xiě)入\n".encode("utf-8")) os.write(2, "往 stderr 里面寫(xiě)入\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ò):文件描述符有問(wèn)題,此時(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 就好。
|