[英]Too many open files using requests module
我正在使用请求模块将几个文件POST到服务器,这在大多数情况下工作正常。 但是,当许多文件上传> 256时,我得到一个IOError:[Errno 24]打开的文件太多。 出现问题的原因是我构建了一个包含许多文件的字典,这些文件的打开方式如下面的代码所示。 由于我没有关闭这些打开文件的句柄,因此我们看到此错误。 这导致以下问题
请求模块是否自动关闭打开的文件?
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.