2017-06-15 80 views
20

我正在研究一個腳本,它讀取文件的文件夾(每個文件的大小從20 MB到100 MB),修改每行中的一些數據,然後回寫該文件的副本。Python writelines()和write()巨大的時差

with open(inputPath, 'r+') as myRead: 
    my_list = myRead.readlines() 
    new_my_list = clean_data(my_list) 
with open(outPath, 'w+') as myWrite: 
    tempT = time.time() 
    myWrite.writelines('\n'.join(new_my_list) + '\n') 
    print(time.time() - tempT) 
print(inputPath, 'Cleaning Complete.') 

在具有90 MB文件(〜90萬線)運行此代碼,就印刷140秒作爲寫入到文件中取出的時間。這裏我用writelines()。所以我搜索了不同的方法來提高文件寫入速度,在我閱讀的大多數文章中,它說write()writelines()不應該顯示任何區別,因爲我正在編寫單個連接的字符串。我還檢查採取只有以下語句時:

new_string = '\n'.join(new_my_list) + '\n' 

前後歷時僅爲0.4秒,所以採取了大量的時間,是因爲創建列表中沒有。 只是嘗試write()我試過這段代碼:

with open(inputPath, 'r+') as myRead: 
    my_list = myRead.readlines() 
    new_my_list = clean_data(my_list) 
with open(outPath, 'w+') as myWrite: 
    tempT = time.time() 
    myWrite.write('\n'.join(new_my_list) + '\n') 
    print(time.time() - tempT) 
print(inputPath, 'Cleaning Complete.') 

它打印2.5秒。爲什麼在write()writelines()的文件寫入時間中存在如此大的差異,即使它是相同的數據?這是正常的行爲還是在我的代碼中有錯?輸出文件對於這兩種情況似乎都是相同的,所以我知道數據沒有丟失。

+2

upvote找到扭曲的方式使用writelines與預期的結果,並找到一個意外的警告。 –

+0

另外我的clean_data()函數去除每一行,所以額外的換行符被刪除。 –

回答

37

file.writelines()預計可迭代的字符串。然後它繼續循環併爲迭代器中的每個字符串調用file.write()。在Python中,方法做到這一點:

def writelines(self, lines) 
    for line in lines: 
     self.write(line) 

你傳遞一個大的字符串,字符串爲字符串迭代了。當迭代你得到單個字符,字符串的長度爲1.所以實際上你正在len(data)分開調用file.write()。這很慢,因爲您一次只能建立一個寫緩衝區的單個字符。

請勿將單個字符串傳遞到file.writelines()。代之以傳遞列表或元組或其他迭代。

你可以在各行添加了新行發送一個生成器表達式,例如:

myWrite.writelines(line + '\n' for line in new_my_list) 

現在,如果你可以做clean_data()一個發電機,產生清潔線,你可以流從數據輸入文件,通過你的數據清洗髮電機,並輸出到輸出文件,而無需使用任何更多的內存比所需的讀寫緩衝區,但是需要多狀態來清潔您的線路:

with open(inputPath, 'r+') as myRead, open(outPath, 'w+') as myWrite: 
    myWrite.writelines(line + '\n' for line in clean_data(myRead)) 

另外,我會考慮更新clean_data()以排出包含換行符的行。

+0

'myWrite.writelines('\ n'.join(my_list)+'\ n')'只能是myWrite.writelines(「{} \ n」.format(x)for my_list)''更快;沒有列表建立。 –

+0

@ Jean-FrançoisFabre:這就是爲什麼我要傳入一個列表或元組*或其他可迭代*。 :-) –

+0

@ Jean-FrançoisFabre:然而,它可能只是一種節省內存的措施,因爲緩衝區仍將這些行連接在一起,直到它已滿。如果'clean_data()'是一個生成器,它會有所幫助。 –

2

'write(arg)'方法需要字符串作爲其參數。所以一旦它調用,它會直接寫入。這是它速度更快的原因。 如果你使用writelines()方法,它期望字符串列表作爲迭代器。所以即使你發送數據到writelines,它也會假設它有迭代器,它會嘗試迭代它。因爲它是一個迭代器,所以需要一些時間來迭代和寫入它。

這是明確的嗎?

+0

但它仍然是一個單一的字符串是不是?它會迭代1個值?這將如何影響寫入速度? –

+1

是的,你可能想要建議像'myWrite.writelines(['\ n'.join(my_list)+'\ n'])' – mgilson

+3

@ArjunBalgovind:單個字符串是一個單獨的字符迭代。 –

5

爲補充的Martijn答案,最好的辦法是避免建設擺在首位使用join列表

只需通過一臺發電機理解到writelines,加入到底換行:沒有不必要的內存分配和沒有循環(除了理解)

myWrite.writelines("{}\n".format(x) for x in my_list)