使用请求模块打开的文件太多

时间:2013-11-27 23:28:16

标签: python python-requests

我正在使用请求模块将多个文件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
    
  3. 我现在使用的解决方法是上传后的files.clear()<一次256个文件。我不确定这些文件是否会关闭,但错误消失了。

    请提供有关如何处理此情况的见解。感谢

2 个答案:

答案 0 :(得分:4)

这里最简单的解决方案是自己将文件读入内存,然后将它们传递给请求。请注意,正如the docs所说,“如果需要,您可以发送字符串作为文件接收”。所以,做到这一点。

换句话说,不是像这样建立一个字典:

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所有文件的所有数据,如果你不这样做。*

但同时,让我回答你的实际问题,而不是仅仅告诉你该怎么做。


  

由于我没有关闭这些打开文件的句柄,因此我们看到了这个错误。

当然可以。你有一个字典,其值是这些打开的文件。

事实上,如果你没有拥有它们的句柄,这个问题可能会发生得少得多,因为垃圾收集器会(通常,但不一定非常强大/可靠地计算) on)为你照顾好事。事实上它从未这样做意味着你必须拥有它们的句柄。


  

有没有办法以块的形式关闭这些文件?

不确定。我不知道你是如何做这些块的,但大概每个块都是一个键或一些东西的列表,你正在通过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 - 我相信这也意味着您必须手动执行多部分。但是如果你有足够的页面空间,只是没有足够的真实RAM,所以试图读取它们都会让你陷入交换震撼地狱,你可以通过在磁盘上连接它们来打开它,打开巨大的文件,{{ 1}} ping它的片段,并将它们作为字符串发送......

答案 1 :(得分:4)

不要忘记python duck typing的力量!

只需为您的文件实现包装类:

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遍历字典时,每个文件一次打开读取和关闭。

请注意,虽然这个答案和abarnert的答案现在基本上做同样的事情,requests将来可能不会完全在内存中构建请求然后发送它,但是在流中发送每个文件,保持低内存使用率。那时这段代码会更有效率。

相关问题