【问题标题】:How to read lines from arbitrary BZ2 streams for CSV?如何从任意 BZ2 流中读取 CSV 行?
【发布时间】:2017-12-12 17:45:26
【问题描述】:

bz2 模块提供了一个标准的open() 方法,人们可以从中调用readline()。但是,我的情况是我有一个流(指向大量数据),我想动态解压缩行。我目前的实现如下,但我知道必须有更简洁的方法来做到这一点。

import bz2
import csv

BZ2_BUFFER = ''

BZ2_DECOMPRESSOR = None

BZ2_FILE = None

BZ2_READ_SIZE = 100 * 1024


def bz2_csv_rows(fp):
    global BZ2_BUFFER, BZ2_DECOMPRESSOR, BZ2_FILE, BZ2_READ_SIZE

    BZ2_BUFFER = ''
    BZ2_DECOMPRESSOR = bz2.BZ2Decompressor()
    BZ2_FILE = fp

    for row in csv.reader(iter(bz2_line_reader, b'')):
        yield row


def bz2_line_reader():
    global BZ2_BUFFER, BZ2_DECOMPRESSOR, BZ2_FILE, BZ2_READ_SIZE

    if BZ2_BUFFER is None:
        return None

    while '\n' not in BZ2_BUFFER:
        bindata = BZ2_FILE.read(BZ2_READ_SIZE)

        try:
            data = BZ2_DECOMPRESSOR.decompress(bindata)
        except EOFError:
            break
        except IOError:
            pass

        BZ2_BUFFER += data

        if len(data) < BZ2_READ_SIZE:
            BZ2_FILE = None
            break

    i = BZ2_BUFFER.find('\n')
    if i is None or i < 0:
        line = BZ2_BUFFER
        BZ2_BUFFER = None
        return line

    line = BZ2_BUFFER[:i]
    BZ2_BUFFER = BZ2_BUFFER[i + 1:]
    return line

想法?

【问题讨论】:

  • 什么是 BZ2FILE,或者你从哪里得到它?
  • 恕我直言,解压缩流上的 io.TextIOWrapper 就是您所需要的,但我不明白您如何获取数据...

标签: python python-2.7 csv bz2


【解决方案1】:

这里有一些更简洁的东西,并且(在我看来)它更具可读性并且摆脱了您的代码使用的所有那些讨厌的全局变量:

import bz2
import csv
from functools import partial

class BZ2_CSV_LineReader(object):
    def __init__(self, filename, buffer_size=4*1024):
        self.filename = filename
        self.buffer_size = buffer_size

    def readlines(self):
        with open(self.filename, 'rb') as file:
            for row in csv.reader(self._line_reader(file)):
                yield row

    def _line_reader(self, file):
        buffer = ''
        decompressor = bz2.BZ2Decompressor()
        reader = partial(file.read, self.buffer_size)

        for bindata in iter(reader, b''):
            block = decompressor.decompress(bindata).decode('utf-8')
            buffer += block
            if '\n' in buffer:
                lines = buffer.splitlines(True)
                if lines:
                    buffer = '' if lines[-1].endswith('\n') else lines.pop()
                    for line in lines:
                        yield line

if __name__ == '__main__':

    bz2_csv_filename = 'test_csv.bz2'
    for row in BZ2_CSV_LineReader(bz2_csv_filename).readlines():
        print(row)

【讨论】:

  • 感谢您的代码 - 这太棒了!要使此代码与 Python 3 兼容,请将行 block = decompressor.decompress(biodata) 更改为 block = decompressor.decompress(bindata).decode("utf-8")
  • @Demitri:不客气……很高兴听到你的建议,但由于这个问题被标记为“python-2.7”,我不会改变我的回答到那个。
  • 哦,不,我不希望你这样做,只是添加它来帮助任何想在 Python 3 中使用它的人(或者将来可能会再次找到这个答案的我)。
  • @Demitri:决定将您提到的更改合并到答案中,使其在 Python 2 和 3 中都可以工作,以便对更多人有用。
【解决方案2】:

也许它会有用:我使用 Python 3,并且我有一个大的 csv.bz2 文件。 我是这样处理的:

import bz2
import csv

def bz2_csv_rows(fp):
    with bz2.open(fp, mode='rt', newline='') as bzfp:
        for row in csv.reader(bzfp):
            yield row

主要功能是在文本模式下打开流:在调用 bz2.open() 时使用 mode='rt' 而不是在二进制模式下手动搜索“\n”。但我不确定这是否适用于非物理文件。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多