Tengo un iterable de bytes , como
bytes_iter = ( b'col_1,', b'c', b'ol_2\n1', b',"val', b'ue"\n', ) (pero por lo general esto no estaría codificado o disponible de una vez, sino que se suministraría desde un generador, por ejemplo) y quiero convertir esto en una iteración de líneas str , donde los saltos de línea se desconocen por adelantado, pero podrían ser cualquiera de \r , \n o \r\n . Así que en este caso sería:
lines_iter = ( 'col_1,col_2', '1,"value"', )(pero de nuevo, solo como un iterable, no para que esté todo en la memoria a la vez).
¿Cómo puedo hacer esto?
Contexto: mi objetivo es pasar el iterable de las líneas str a csv.reader (¿que creo que necesita líneas completas?), Pero estoy interesado en esta respuesta en general.
Usé yield y re.finditer .
La expresión de rendimiento se usa cuando se define una función de generador o una función de generador asíncrono y, por lo tanto, solo se puede usar en el cuerpo de una definición de función. El uso de una expresión de rendimiento en el cuerpo de una función hace que esa función sea una función generadora
Devuelve un iterador que produce objetos de coincidencia sobre todas las coincidencias que no se superponen para el patrón RE en la cadena. La cadena se escanea de izquierda a derecha y las coincidencias se devuelven en el orden encontrado. Los partidos vacíos se incluyen en el resultado.
Si no hay grupos, devuelva una lista de cadenas que coincidan con el patrón completo. Si hay exactamente un grupo, devuelva una lista de cadenas que coincidan con ese grupo. Si hay varios grupos presentes, devuelve una lista de tuplas de cadenas que coinciden con los grupos. Los grupos que no capturan no afectan la forma del resultado.
La expresión regular ([^\r\n]*)(\r\n|\r|\n)? se puede dividir en dos partes para que coincida (es decir, dos grupos). El primer grupo coincide con los datos sin \r y \n , y el segundo grupo coincide con \r , \n o \r\n .
import re find_rule = re.compile("([^\r\n]*)(\r\n|\r|\n)?") def converter(byte_data): left_d = "" for d in byte_data: # Used to save the previous match result in the `for` loop prev_result = None # Concatenate the last part of the previous data with the current data, # used to deal with the case of `\r\n` being separated. d = left_d + d.decode() left_d = "" # Using `find_rule.finditer` the last value("") will be invalid for match_result in find_rule.finditer(d): i = match_result.group() if not i: # The program comes to this point, indicating that i == "", which is the last matching value left_d, prev_result = prev_result.group(), None continue if prev_result: if prev_result.group(2) is None: # The program goes here, represented as the last valid value matched left_d = prev_result.group() else: # Returns the previous matched value yield prev_result.group() # Save the current match result prev_result = match_result else: yield left_d for i in (converter(iter(( b'col_1,\r', b'\nc', b'ol_2\n1', b'\n,"val;\r', b'ue"\n'))) ): print(repr(i))Producción:
'col_1,\r\n' 'col_2\n' '1\n' ',"val;\r' 'ue"\n'Use el módulo io para hacer la mayor parte del trabajo por usted:
class ReadableIterator(io.IOBase): def __init__(self, it): self.it = iter(it) def read(self, n): # ignore argument, nobody actually cares # note that it is *critical* that we suppress the `StopIteration` here return next(self.it, b'') def readable(self): return True luego simplemente llame a io.TextIOWrapper(ReadableIterator(some_iterable_of_bytes)) .
Tal vez me estoy perdiendo algo importante (o sutil) porque algunas de las respuestas votadas parecen un poco más exóticas que esto, pero creo que puedes decodificar y encadenar los bytes y usar itertools.groupby para obtener un generador de cadenas:
from itertools import groupby, chain bytes_iter = ( b'col_1,', b'c', b'ol_2\n', b'1,"val;', b'ue"\n' ) def make_strings(G): strings = chain.from_iterable(map(bytes.decode, G)) for k, g in groupby(strings, key=lambda c: c not in '\n\r'): if k: yield ''.join(g) list(make_strings(bytes_iter)) # ['col_1,col_2', '1,"val;ue"']Poner @o11c's y @user2357112 apoya las contribuciones de Monica juntas:
import codecs import csv import io def yield_bytes(): chunks = [ b'col_1,', b'c', b'ol_2\n1', b',"val', b'ue"\n', b'Hello,' b'\xe4\xb8', b'\x96', b'\xe7', b'\x95\x8c\n' b'\n' ] for chunk in chunks: yield(chunk) decoder = codecs.getincrementaldecoder('utf-8')() def yield_encoded_bytes(): s = None for bytes in yield_bytes(): s = decoder.decode(bytes, final=False) if s: yield s.encode('utf-8') class ReadableIterator(io.IOBase): def __init__(self, it): self.it = iter(it) def read(self, n): # ignore argument, nobody actually cares # note that it is *critical* that we suppress the `StopIteration` here return next(self.it, b'') def readable(self): return True f = io.TextIOWrapper(ReadableIterator(yield_encoded_bytes())) for row in csv.reader(f): print(row)y obtengo:
['col_1', 'col_2'] ['1', 'value'] ['Hello', '世界'] []