Python3 Buffered I/O淺析
f = open("apache.log", "r+")nprint(f.tell())nf.read(1)nprint(f.tell())nf.write("Love")nf.close()n
控制太輸出的結果是0,1
文件操作後的結果是Hello World!!!Loven
問題:代碼讀了一個字元,指針應該到第二個字元的位置,為什麼寫內容還是被追加到了文件的最後?
太長不看版結論:Python3之後的New I/O行為,實現了bufferd I/O ,導致了這個看似奇怪的行為
看到這個問題,首先試了試Python2.7 和Python3,發現Python3的open的行為和Python2確實是有所區別的。
f = open("apache.log", "r+")nprint(f.tell())nf.read(1)nprint(f.tell())nf.write("Love")n#<-在這裡加一句f.tell()nf.close()n
在write後,f.tell()已經是原來的文件長+新寫入的字元長了
這個問題在文件操作中,也就是APUE里的一個普通的seek問題,我們可以看看為什麼在Python3、在哪裡導致的這樣的問題
在Python3中,open實際上是io標準庫里的io.open:
16.2. io - Core tools for working with streams - Python 3.5.2 documentationio.open(file, mode=r, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)nThis is an alias for the builtin open() function.n
而不再是Python2里的內建函數了
open返回的是:
In [2]: fnOut[2]: <_io.TextIOWrapper name=test.txt mode=r+ encoding=UTF-8>n
下劃線開頭的這個_io是一個c模塊:
In [4]: _io?nType: modulenString form: <module io (built-in)>nDocstring:nThe io module provides the Python interfaces to stream handling. Thenbuiltin open function is defined in this module.n
它是用來提供流處理的介面的,繼承關係什麼的在上面的鏈接里有說。
看了下Python3.5的代碼,TextIOWrapper的write實現在textio.c:_io_TextIOWrapper_write_impl這個函數里
看了一下PEP 3116 -- New I/O,和裡面的代碼,確定導致這個問題的邏輯核心是:
if (self->pending_bytes_count > self->chunk_size || needflush ||n text_needflush) {n if (_textiowrapper_writeflush(self) < 0)n return NULL;n }nn if (needflush) {n ret = PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_flush, NULL);n if (ret == NULL)n return NULL;n Py_DECREF(ret);n }n
這篇PEP設計了一個三層的Python3 IO實現:raw層、Buffer層、Text層,我們通常直接用的Text層。告訴我們,打開一個流對象後(例如Open),這個對象會有一個buffer屬性。作為緩衝層對應的對象。
上面貼的代碼描述了當write調用時,實際上調用的是self->buffer的寫相關的方法。我們可以實驗一下:
$ cat test.txt nhello worldnn# zhutou @ Phoenix in ~ [1:19:01] n$ cat test.txt|wcn 1 2 12n
In [1]: f = open("test.txt","r+")nOut[1]: <_io.TextIOWrapper name=test.txt mode=r+ encoding=UTF-8>nnIn [2]: f.read(1)nOut[2]: hnnIn [3]: f.buffernOut[3]: <_io.BufferedRandom name=test.txt>nnIn [4]: f.buffer.tell()nOut[4]: 12n
static intn_textiowrapper_writeflush(textio *self)n{n PyObject *pending, *b, *ret;n pending = self->pending_bytes;n b = _PyBytes_Join(_PyIO_empty_bytes, pending);nn do {n ret = PyObject_CallMethodObjArgs(self->buffer,n _PyIO_str_write, b, NULL);n } while (ret == NULL && _PyIO_trap_eintr());n return 0;n}n
這一點在Python中可以得到驗證:
In [5]: f.buffer.seek(1)nOut[5]: 1nnIn [6]: f.write("Love")nOut[6]: 4nnIn [7]: f.close()n
現在符合直覺了,文件內容已經是hLove world 了~
由此得到了開頭的結論:Python3和Python2的文件行為不一致是文件buffer造成的,buffered IO本身是為了解決讀取文件的效率問題。(PEP中說是為了和Java看齊。。俺不懂什麼java,不知道java會不會有這個問題。。
The new I/O spec is intended to be similar to the Java I/O libraries, but generally less confusing
然後還更加優越2333
在io.TextIOWrapper類中,我們可以構造時指定write_through=True 避免寫的時候經過buffer
但我找了一下open函數的文檔和對應的實現,沒有找到改這個參數的地方~推薦閱讀:
※Python入門進階推薦書單
※Python實踐20-閉包簡介
TAG:Python |