2017-12-12 2 views
2

Das bz2 Modul bietet eine Standard open() Methode, von der man readline() anrufen kann. Meine Situation ist jedoch eine Situation, in der ich einen Stream habe (der auf eine große Datenmenge zeigt), den ich im laufenden Betrieb entpacken möchte. Meine aktuelle Implementierung ist wie folgt, aber ich weiß, dass es einen knapperen Weg geben muss, dies zu tun.Wie liest man Zeilen aus beliebigen BZ2-Streams für CSV?

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 

Gedanken?

+0

Was ist BZ2FILE oder wo kommt es her? –

+0

IMHO, ein io.TextIOWrapper über den dekomprimierten Stream ist alles was Sie brauchen, aber ich konnte nicht verstehen, wie Sie Ihre Daten bekommen ... –

Antwort

2

Hier ist etwas, das ein wenig prägnanter ist, und (meiner Meinung nach) es ist besser lesbar und wird von all den bösen globalen Variablen Ihr Code verwendet los:

import bz2 
import csv 
import sys 

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() 

     while True: 
      bindata = file.read(self.buffer_size) 
      if not bindata: # EOF? 
       break # Note: Could leave an incomplete "line" in the buffer 
         # (but there shouldn't be one in an CSV file). 

      block = decompressor.decompress(bindata) 
      if sys.version_info >= (3,): # Python 3 
       block = block.decode('utf-8') # Convert bytes to string. 

      if block: 
       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 

Anwendungsbeispiel:

bz2_csv_filename = 'test_csv.bz2' 
for row in BZ2__CSV_LineReader(bz2_csv_filename).readlines(): 
    print(row) 
+0

@Demitri: Danke, guten Fang! – martineau

+0

Danke für den Code - das ist ausgezeichnet! Um diesen Code mit Python 3 kompatibel zu machen, ändern Sie die Zeile 'block = decompressor.decompress (biodata)' in 'block = decompressor.decompress (bindata) .decode (" utf-8 ")'. – Demitri

+0

@Demitri: Gern geschehen ... das ist gut zu hören und Ihr Vorschlag sieht nützlich aus, aber da diese Frage mit "python-2.7" getaggt ist, werde ich meine Antwort in Bezug darauf nicht ändern. – martineau

Verwandte Themen