簡體   English   中英

使用請求模塊打開的文件太多

[英]Too many open files using requests module

我正在使用請求模塊將幾個文件POST到服務器,這在大多數情況下工作正常。 但是,當許多文件上傳> 256時,我得到一個IOError:[Errno 24]打開的文件太多。 出現問題的原因是我構建了一個包含許多文件的字典,這些文件的打開方式如下面的代碼所示。 由於我沒有關閉這些打開文件的句柄,因此我們看到此錯誤。 這導致以下問題

  1. 有沒有辦法以塊的形式關閉這些文件?
  2. 請求模塊是否自動關閉打開的文件?

     url = 'http://httpbin.org/post' #dict with several files > 256 files = {'file1': open('report.xls', 'rb'), 'file2': open('report2.xls', 'rb')} r = requests.post(url, files=files) r.text 

我現在使用的解決方法是一次上傳<256個文件后的files.clear()。 我不確定文件是否關閉這樣做,但錯誤消失了。

請提供有關如何處理這種情況的見解。 謝謝

這里最簡單的解決方案是自己將文件讀入內存,然后將它們傳遞給請求。 請注意,正如文檔所說,“如果需要,您可以發送字符串作為文件接收”。 所以,那樣做。

換句話說,而不是像這樣建立一個字典:

files = {tag: open(pathname, 'rb') for (tag, pathname) in stuff_to_send}

......像這樣構建:

def read_file(pathname):
    with open(pathname, 'rb') as f:
        return f.read()
files = {tag: read_file(pathname) for (tag, pathname) in stuff_to_send}

現在你只保證一次打開一個文件。

這可能看起來很浪費,但實際上並非 - requests只是read所有文件中的所有數據,如果不這樣做的話。*

但同時,讓我回答你的實際問題,而不是僅僅告訴你該怎么做。


由於我沒有關閉這些打開文件的句柄,因此我們看到此錯誤。

當然可以。 你有一個dict,其值是這些打開的文件。

事實上,如果你沒有它們的句柄,這個問題可能會發生得少得多,因為垃圾收集器會(通常,但不一定非常強大/可靠地指望)為你照顧好事情。 從來沒有這樣做的事實意味着你必須掌握它們。


有沒有辦法以塊的形式關閉這些文件?

當然。 我不知道你是怎么做的,但是大概是每個塊都是一個鍵或者什么東西,你傳遞的files = {key: files[key] for key in chunk} ,對吧?

所以,在請求之后,執行以下操作:

for key in chunk:
    files[key].close()

或者,如果你正在為每個塊構建一個dict ,如下所示:

files = {tag: open(filename, 'rb') for (tag, filename) in chunk}

......這樣做:

for file in files.values():
    file.close()

請求模塊是否自動關閉打開的文件?

不,你必須手動完成。

在許多用例中,你永遠不會這樣做,因為files變量在請求后很快消失了,一旦沒有人對dict有引用,它很快就會被清除(立即使用CPython並且如果沒有循環;只是“很快”,如果其中任何一個不是真的),意味着很快就會清理所有文件,此時析構函數會為你關閉它們。 但你不應該依賴它。 始終明確關閉文件。

而且files.clear()似乎工作的原因是它正在做同樣的事情讓files消失:它迫使dict忘記所有文件,這刪除了對每個文件的最后一個引用,這意味着它們將被清除很快,等等


*如果您沒有足夠的頁面空間將它們全部保存在內存中怎么辦? 那么你無論如何都不能一次性發送它們。 您必須提出單獨的請求,或使用流API - 我相信這也意味着您必須手動執行多部分。 但是如果你有足夠的頁面空間,只是沒有足夠的真實內存,那么嘗試閱讀它們都會讓你陷入交換震撼地獄,你或許可以通過在磁盤上連接它們來打開它,打開巨型文件, mmap ping它的一部分,並將它們作為字符串發送......

不要忘記蟒蛇鴨打字的力量!

只需為您的文件實現一個包裝類:

class LazyFile(object):

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def read(self):
        with open(self.filename, self.mode) as f:
            return f.read()

url = 'http://httpbin.org/post'
#dict with a billion files
files = {'file1': LazyFile('report.xls', 'rb'), 'file2': LazyFile('report2.xls', 'rb')}
r = requests.post(url, files=files)
r.text

通過這種方式,當requests遍歷dict時,每個文件一次打開讀取和關閉。

請注意,雖然這個答案和abarnert的答案現在基本上做同樣的事情,但是將來, requests可能不會完全在內存中構建請求然后發送它,而是在流中發送每個文件,從而保持低內存使用率。 那時這段代碼會更有效。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM