<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
無論是讀檔案還是寫檔案,都要先開啟檔案。說到開啟檔案,估計首先想到的就是內建函數 open(即 io.open),那麼它和 os.open 有什麼關係呢?
內建函數 open 實際上是對 os.open 的封裝,在 os.open 基礎上增加了相關存取方法。因此為了操作方便,應該呼叫內建函數 open 進行檔案操作,但如果對效率要求較高的話,則可以考慮使用 os.open。
此外 open 函數返回的是一個檔案物件,我們可以在此基礎上進行任意操作;而 os.open 返回的是一個檔案描述符,說白了就是一個整數,因為每一個檔案物件都會對應一個檔案描述符。
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 函數實際上是封裝了 C 的 fopen,C 的 fopen 又封裝了系統呼叫提供的 open。
作業系統提供了很多的系統呼叫,開啟檔案則是 open,我們看到它返回一個整數,這個整數就是對應的檔案描述符。C 的 fopen 封裝了系統呼叫的 open,返回的是一個檔案指標。
所以內建函數 open 和 os.open 的區別就更加清晰了,內建函數 open 在底層會使用 C 的 fopen,得到的是一個封裝好的檔案物件,在此基礎上可以直接操作。至於 os.open 在底層則不走 C 的 fopen,而是直接使用系統呼叫提供的 open,得到的是檔案描述符。
os 模組內部的函數基本上都是直接走的系統呼叫,所以模組名才叫 os。
然後我們使用 os.open 一般需要傳遞兩個引數,第一個引數是檔名,第二個引數是模式,舉個栗子:
import os # 以唯讀方式開啟,要求檔案必須存在 # 開啟時遊標處於檔案的起始位置 os.open("main.c", os.O_RDONLY) # 以只寫方式開啟,要求檔案必須存在 # 開啟時遊標處於檔案的起始位置 os.open("main.c", os.O_WRONLY) # 以可讀可寫方式開啟,要求檔案必須存在 # 開啟時遊標處於檔案的起始位置 os.open("main.c", os.O_RDWR) # 以唯讀方式開啟,檔案不存在則建立 # 存在則不做任何事情,等價於 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) # 檔案不存在時建立,存在時清空 # 開啟時遊標處於檔案的起始位置 os.open("main.c", os.O_WRONLY | os.O_CREAT | os.O_TRUNC) # 當然讀取檔案也是可以的 # 比如 os.O_RDONLY | os.O_CREAT | os.O_TRUNC # 也是檔案存在時清空內容,但是這沒有任何意義 # 因為讀取的時候將檔案清空了,那還讀什麼? # 檔案不存在時建立,存在時追加 # 開啟時遊標處於檔案的末尾 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 接收兩個引數,第一個引數是檔案描述符,第二個引數是要讀取多少個位元組。
import os fd = os.open("main.c", os.O_RDONLY) # 使用 os.read 進行讀取 # 這裡讀取 20 個位元組 data = os.read(fd, 20) print(data) """ b'#include <Python.h>' """ # 再讀取 20 個位元組 data = os.read(fd, 20) print(data) """ b'n#include <ctype.h>' """ # 繼續讀取 data = os.read(fd, 20) # 由於只剩下一個位元組 # 所以就讀取了一個位元組 # 顯然此時檔案已經讀完了 print(data) """ b'n' """ # 檔案讀取完畢之後 # 再讀取的話會返回空位元組串 print(os.read(fd, 20)) # b'' print(os.read(fd, 20)) # b'' print(os.read(fd, 20)) # b''
所以這就是檔案的讀取方式,還是很簡單的。然後在讀取的過程中,我們還可以移動遊標,通過 os.lseek 函數。
如果 m 大於 0,表示向後移動,m 小於 0,表示向前移動。所以當第三個引數為 2 的時候,也就是結束位置,那麼 m 一般為負數。因為相對於結束位置,肯定要向前移動,當然向後移動也可以,不過沒啥意義;同理當第三個引數為 0 時,m 一般為正數,相對於起始位置,肯定要向後移動。
import os fd = os.open("main.c", os.O_RDONLY) data = os.read(fd, 20) print(data) """ b'#include <Python.h>' """ # 從檔案的起始位置向後移動 0 個位元組 # 相當於將遊標設定在檔案的起始位置 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 個位元組 # 沒錯,此時作用類似於 input while True: data = os.read(0, 10).strip() print(f"你輸入了:", data) if data == b"exit": break
我們測試一下:
os.read 可以實現 input 的效果,並且效率更高。另外當按下回車時,換行符也會被讀進去,所以需要 strip 一下。然後我們這裡讀的是 10 個位元組,如果一次讀不完,那麼會分多次讀取。在讀取檔案的時候,也是同理。
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 還可以和內建函數 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 位元組 f.seek(5, 0) print(f.tell()) # 5 # os.lseek 也可以實現 os.lseek(f.fileno(), 3, 0) print(f.tell()) # 3 # 此時會從第 4 個位元組開始讀取 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 """ # 但如果要相對檔案末尾移動具體的位元組數 # 那麼 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 即可寫入。
import os # 此時可讀可寫,檔案不存在時自動建立,存在則清空 fd = os.open("1.txt", os.O_RDWR | os.O_CREAT | os.O_TRUNC) # 寫入內容,接收兩個引數 # 引數一:檔案描述符;引數二:bytes 物件 os.write(fd, b"hello, ") os.write(fd, "古明地覺".encode("utf-8")) # 讀取內容 data = os.read(fd, 1024) print(data) # b'' # 問題來了,為啥讀取不到內容呢? # 很簡單,因為遊標會伴隨著資料的寫入而不斷後移 # 這樣的話,資料才能不斷地寫入 # 因此,現在的遊標位於檔案的結尾處 # 想要檢視寫入的內容需要移動到開頭 os.lseek(fd, 0, 0) print(os.read(fd, 1024).decode("utf-8")) """ hello, 古明地覺 """ # 從後往前移動 3 位元組 os.lseek(fd, -3, 2) print(os.read(fd, 1024).decode("utf-8")) """ 覺 """
以上就是檔案的寫入,當然它也可以和內建函數 open 結合,通過 os.write(f.fileno(), b"xxx") 進行寫入。但是不建議 os.open 和 open 混用,其實工作中使用 open 就足夠了。
然後是 stdout 和 stderr,和 os.write 結合可以實現 print 的效果。
import os os.write(1, "往 stdout 裡面寫入n".encode("utf-8")) os.write(2, "往 stderr 裡面寫入n".encode("utf-8"))
執行一下,檢視控制檯:
以上就是 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 """
如果是呼叫 f.close() 關閉檔案,再進行讀取的話,會丟擲一個 ValueError,提示 I/O operation on closed file。這個報錯資訊比較明顯,不應該在關閉的檔案上執行 IO 操作,因為檔案物件知道檔案已經關閉了,畢竟呼叫的是自己的 close 方法,所以這個報錯是直譯器給出的。當然啦,呼叫 f.close 也會觸發 os.close,因為關閉檔案最終還是要交給作業系統負責的。
但如果是直接關閉底層的檔案描述符,檔案物件是不知道的,再使用 f.read() 依舊會觸發系統呼叫,也就是 os.read。而作業系統發現檔案已經關閉了,所以會報錯:檔案描述符有問題,此時就是一個 OSError,報錯資訊是作業系統給出的。
import os f = open(r"1.txt", "r") # 檔案是否關閉 print(f.closed) # False os.close(f.fileno()) print(f.closed) # False # 所以呼叫 os.close,檔案物件 f 並不知道 # f.read 依舊會觸發系統呼叫
如果是使用 f.close()。
f = open(r"1.txt", "r") f.close() print(f.closed) # True
後續執行 IO 操作,就不會再走系統呼叫了,而是直接丟擲 ValueError,原因是檔案物件知道檔案已經關閉了。
除了 os.close 之外,還有一個 os.closerange,可以關閉多個檔案描述符對應的檔案。
import os # 關閉檔案描述符為 1、2、3、4 的檔案 os.closerange(1, 5)
該方法不是很常用,瞭解一下即可。
以上就是使用 os 模組操作檔案,它是直接使用作業系統提供的系統呼叫,所以效率上會比內建函數 open 要高一些。但是工作中還是不太建議使用 os 模組操作檔案,使用內建函數 open 就好。
到此這篇關於Python使用os模組實現更高效地讀寫檔案的文章就介紹到這了,更多相關Python os模組讀寫檔案內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45