在io.TextIOWrapper

时间:2017-12-26 22:10:25

标签: python python-3.x urllib3

我使用AWS boto3库,它返回urllib3.response.HTTPResponse的实例。该响应是io.IOBase的子类,因此表现为二进制文件。其read()方法返回bytes个实例。

现在,我需要解码以这种方式接收的文件中的csv数据。我希望我的代码能够以最小的代码开销在py2py3上工作,因此我使用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的实现上,因为它总是希望底层对象有TextIOWrappersource),而read1& #39;对python3存在的实施检查,并优雅地回到read1source)。

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结尾,则问题不会发生,一切正常。

\nsource)中关闭底层对象还有一些额外的逻辑,这似乎导致了问题。

问题是:如何编写代码

  • 同时处理python2和python3,最好没有try..catch或依赖于版本的分支;
  • 正确处理以HTTPResponse表示的CSV文件,无论它们是否以HTTPResponse结尾?

一种可能的解决方案是围绕\n创建一个自定义包装器,当对象关闭而不是提升TextIOWrapper时,read()会返回b''。但有没有更好的解决方案,没有这样的黑客攻击?

1 个答案:

答案 0 :(得分:3)

看起来这是urllib3.HTTPResponsefile对象之间的接口不匹配。它在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维护者在错误报告评论中提出了类似的修复,但这将是一个突破性的变化,因此现在事情可能不会改变,所以我必须使用包装器(或做一些猴子修补,这是可能更糟糕。)