我使用AWS boto3
库,它返回urllib3.response.HTTPResponse
的实例。该响应是io.IOBase
的子类,因此表现为二进制文件。其read()
方法返回bytes
个实例。
现在,我需要解码以这种方式接收的文件中的csv
数据。我希望我的代码能够以最小的代码开销在py2
和py3
上工作,因此我使用backports.csv
作为输入而不是py2' s io.IOBase
个对象。
第一个问题是file()
会为CSV文件生成HTTPResponse
个数据,而我bytes
需要csv.reader
个数据。
str
我尝试用>>> import io
>>> from backports import csv # actually try..catch statement here
>>> from mymodule import get_file
>>> f = get_file() # returns instance of urllib3.HTTPResponse
>>> r = csv.reader(f)
>>> list(r)
Error: iterator should return strings, not bytes (did you open the file in text mode?)
包裹HTTPResponse
并收到错误io.TextIOWrapper
。这是预期的,因为'HTTPResponse' object has no attribute 'read1'
旨在与TextIOWrapper
个对象一起使用,而不是BufferedIOBase
个对象。它只发生在IOBase
python2
的实现上,因为它总是希望底层对象有TextIOWrapper
(source),而read1
& #39;对python3
存在的实施检查,并优雅地回到read1
(source)。
read
然后我尝试用>>> f = get_file()
>>> tw = io.TextIOWrapper(f)
>>> list(csv.reader(tw))
AttributeError: 'HTTPResponse' object has no attribute 'read1'
和HTTPResponse
包裹io.BufferedReader
。我收到以下错误:
io.TextIOWrapper
经过一些调查后发现只有当文件没有以>>> f = get_file()
>>> br = io.BufferedReader(f)
>>> tw = io.TextIOWrapper(br)
>>> list(csv.reader(f))
ValueError: I/O operation on closed file.
结尾时才会出现错误。如果它以\n
结尾,则问题不会发生,一切正常。
在\n
(source)中关闭底层对象还有一些额外的逻辑,这似乎导致了问题。
问题是:如何编写代码
HTTPResponse
表示的CSV文件,无论它们是否以HTTPResponse
结尾?一种可能的解决方案是围绕\n
创建一个自定义包装器,当对象关闭而不是提升TextIOWrapper
时,read()
会返回b''
。但有没有更好的解决方案,没有这样的黑客攻击?
答案 0 :(得分:3)
看起来这是urllib3.HTTPResponse
和file
对象之间的接口不匹配。它在this urllib3
issue #1305中描述。
目前还没有修复,因此我使用了以下包装代码,看起来效果很好:
class ResponseWrapper(io.IOBase):
"""
This is the wrapper around urllib3.HTTPResponse
to work-around an issue shazow/urllib3#1305.
Here we decouple HTTPResponse's "closed" status from ours.
"""
# FIXME drop this wrapper after shazow/urllib3#1305 is fixed
def __init__(self, resp):
self._resp = resp
def close(self):
self._resp.close()
super(ResponseWrapper, self).close()
def readable(self):
return True
def read(self, amt=None):
if self._resp.closed:
return b''
return self._resp.read(amt)
def readinto(self, b):
val = self.read(len(b))
if not val:
return 0
b[:len(val)] = val
return len(val)
使用如下:
>>> f = get_file()
>>> r = csv.reader(ResponseWrapper(io.TextIOWrapper(io.BufferedReader(f))))
>>> list(r)
urllib3
维护者在错误报告评论中提出了类似的修复,但这将是一个突破性的变化,因此现在事情可能不会改变,所以我必须使用包装器(或做一些猴子修补,这是可能更糟糕。)