iteradores generadores generador espaƱol codigo python zip

generadores - Crear un archivo zip de un generador en Python?



iteradores y generadores en python (9)

gzip.GzipFile escribe los datos en trozos gzip, que pueden establecer el tamaño de los trozos de acuerdo con el número de líneas leídas de los archivos.

un ejemplo:

file = gzip.GzipFile(''blah.gz'', ''wb'') sourcefile = open(''source'', ''rb'') chunks = [] for line in sourcefile: chunks.append(line) if len(chunks) >= X: file.write("".join(chunks)) file.flush() chunks = []

Tengo una gran cantidad de datos (un par de conciertos) Necesito escribir en un archivo zip en Python. No puedo cargar todo en la memoria de una vez para pasar al método .writestr de ZipFile, y realmente no quiero alimentarlo todo en el disco usando archivos temporales y luego volver a leerlo.

¿Hay alguna manera de alimentar un generador o un objeto similar a un archivo a la biblioteca de ZipFile? ¿O hay alguna razón por la cual esta capacidad no parece ser compatible?

Por archivo zip, me refiero a archivo zip. Como se admite en el paquete de archivo zip de Python.


Algunos (muchos? ¿La mayoría?) Algoritmos de compresión se basan en buscar redundancias en todo el archivo.

Algunas bibliotecas de compresión elegirán entre varios algoritmos de compresión en función de cuál funciona mejor en el archivo.

Creo que el módulo ZipFile hace esto, por lo que quiere ver el archivo completo, no solo piezas a la vez.

Por lo tanto, no funcionará con generadores o archivos para cargar en la memoria. Eso explicaría la limitación de la biblioteca Zipfile.


La biblioteca gzip tomará un objeto similar a un archivo para la compresión.

class GzipFile([filename [,mode [,compresslevel [,fileobj]]]])

Aún necesita proporcionar un nombre de archivo nominal para su inclusión en el archivo zip, pero puede pasar su fuente de datos al archivoobj.

(Esta respuesta difiere de la de Damnweet, en que el foco debe estar en la fuente de datos que se lee incrementalmente, no en el archivo comprimido que se escribe incrementalmente).

Y ahora veo que el interlocutor original no aceptará Gzip :-(


La compresión esencial la realiza zlib.compressobj. ZipFile (en Python 2.5 en MacOSX parece estar compilado). La versión de Python 2.3 es la siguiente.

Puede ver que construye el archivo comprimido en 8k fragmentos. La extracción de la información del archivo de origen es compleja porque muchos atributos del archivo de origen (como el tamaño sin comprimir) se registran en el encabezado del archivo zip.

def write(self, filename, arcname=None, compress_type=None): """Put the bytes from filename into the archive under the name arcname.""" st = os.stat(filename) mtime = time.localtime(st.st_mtime) date_time = mtime[0:6] # Create ZipInfo instance to store file information if arcname is None: zinfo = ZipInfo(filename, date_time) else: zinfo = ZipInfo(arcname, date_time) zinfo.external_attr = st[0] << 16L # Unix attributes if compress_type is None: zinfo.compress_type = self.compression else: zinfo.compress_type = compress_type self._writecheck(zinfo) fp = open(filename, "rb") zinfo.flag_bits = 0x00 zinfo.header_offset = self.fp.tell() # Start of header bytes # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 zinfo.file_size = file_size = 0 self.fp.write(zinfo.FileHeader()) zinfo.file_offset = self.fp.tell() # Start of file bytes if zinfo.compress_type == ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while 1: buf = fp.read(1024 * 8) if not buf: break file_size = file_size + len(buf) CRC = binascii.crc32(buf, CRC) if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) self.fp.write(buf) fp.close() if cmpr: buf = cmpr.flush() compress_size = compress_size + len(buf) self.fp.write(buf) zinfo.compress_size = compress_size else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size # Seek backwards and write CRC and file sizes position = self.fp.tell() # Preserve current position in file self.fp.seek(zinfo.header_offset + 14, 0) self.fp.write(struct.pack("<lLL", zinfo.CRC, zinfo.compress_size, zinfo.file_size)) self.fp.seek(position, 0) self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo



La única solución es volver a escribir el método que utiliza para comprimir archivos para leer desde un búfer. Sería trivial agregar esto a las bibliotecas estándar; Estoy sorprendido de que todavía no se haya hecho. Supongo que hay un gran acuerdo sobre la necesidad de revisar toda la interfaz, y eso parece estar bloqueando cualquier mejora incremental.

import zipfile, zlib, binascii, struct class BufferedZipFile(zipfile.ZipFile): def writebuffered(self, zipinfo, buffer): zinfo = zipinfo zinfo.file_size = file_size = 0 zinfo.flag_bits = 0x00 zinfo.header_offset = self.fp.tell() self._writecheck(zinfo) self._didModify = True zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 self.fp.write(zinfo.FileHeader()) if zinfo.compress_type == zipfile.ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while True: buf = buffer.read(1024 * 8) if not buf: break file_size = file_size + len(buf) CRC = binascii.crc32(buf, CRC) & 0xffffffff if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) self.fp.write(buf) if cmpr: buf = cmpr.flush() compress_size = compress_size + len(buf) self.fp.write(buf) zinfo.compress_size = compress_size else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size position = self.fp.tell() self.fp.seek(zinfo.header_offset + 14, 0) self.fp.write(struct.pack("<LLL", zinfo.CRC, zinfo.compress_size, zinfo.file_size)) self.fp.seek(position, 0) self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo


Esto es 2017. Si todavía está buscando hacer esto elegantemente, use Python Zipstream by allanlei . Hasta ahora, es probablemente la única biblioteca bien escrita para lograr eso.


En caso de que alguien se tropiece con esta pregunta, que sigue siendo relevante en 2017 para Python 2.7, aquí hay una solución de trabajo para un archivo zip de transmisión verdadera, sin necesidad de que la salida sea buscable como en los demás casos. El secreto es establecer el bit 3 del indicador de bits de propósito general (consulte https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT sección 4.3.9.1).

Tenga en cuenta que esta implementación siempre creará un archivo de estilo ZIP64, permitiendo que la transmisión funcione para archivos arbitrariamente grandes. Incluye un feo hack para forzar el final de zip64 del registro de directorio central, así que ten en cuenta que hará que todos los archivos comprimidos escritos por tu proceso se conviertan en estilo ZIP64.

import io import zipfile import zlib import binascii import struct class ByteStreamer(io.BytesIO): '''''' Variant on BytesIO which lets you write and consume data while keeping track of the total filesize written. When data is consumed it is removed from memory, keeping the memory requirements low. '''''' def __init__(self): super(ByteStreamer, self).__init__() self._tellall = 0 def tell(self): return self._tellall def write(self, b): orig_size = super(ByteStreamer, self).tell() super(ByteStreamer, self).write(b) new_size = super(ByteStreamer, self).tell() self._tellall += (new_size - orig_size) def consume(self): bytes = self.getvalue() self.seek(0) self.truncate(0) return bytes class BufferedZipFileWriter(zipfile.ZipFile): '''''' ZipFile writer with true streaming (input and output). Created zip files are always ZIP64-style because it is the only safe way to stream potentially large zip files without knowing the full size ahead of time. Example usage: >>> def stream(): >>> bzfw = BufferedZip64FileWriter() >>> for arc_path, buffer in inputs: # buffer is a file-like object which supports read(size) >>> for chunk in bzfw.streambuffer(arc_path, buffer): >>> yield chunk >>> yield bzfw.close() '''''' def __init__(self, compression=zipfile.ZIP_DEFLATED): self._buffer = ByteStreamer() super(BufferedZipFileWriter, self).__init__(self._buffer, mode=''w'', compression=compression, allowZip64=True) def streambuffer(self, zinfo_or_arcname, buffer, chunksize=2**16): if not isinstance(zinfo_or_arcname, zipfile.ZipInfo): zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname, date_time=time.localtime(time.time())[:6]) zinfo.compress_type = self.compression zinfo.external_attr = 0o600 << 16 # ?rw------- else: zinfo = zinfo_or_arcname zinfo.file_size = file_size = 0 zinfo.flag_bits = 0x08 # Streaming mode: crc and size come after the data zinfo.header_offset = self.fp.tell() self._writecheck(zinfo) self._didModify = True zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 self.fp.write(zinfo.FileHeader()) if zinfo.compress_type == zipfile.ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while True: buf = buffer.read(chunksize) if not buf: break file_size += len(buf) CRC = binascii.crc32(buf, CRC) & 0xffffffff if cmpr: buf = cmpr.compress(buf) compress_size += len(buf) self.fp.write(buf) compressed_bytes = self._buffer.consume() if compressed_bytes: yield compressed_bytes if cmpr: buf = cmpr.flush() compress_size += len(buf) self.fp.write(buf) zinfo.compress_size = compress_size compressed_bytes = self._buffer.consume() if compressed_bytes: yield compressed_bytes else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size # Write CRC and file sizes after the file data # Always write as zip64 -- only safe way to stream what might become a large zipfile fmt = ''<LQQ'' self.fp.write(struct.pack(fmt, zinfo.CRC, zinfo.compress_size, zinfo.file_size)) self.fp.flush() self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo yield self._buffer.consume() # The close method needs to be patched to force writing a ZIP64 file # We''ll hack ZIP_FILECOUNT_LIMIT to do the forcing def close(self): tmp = zipfile.ZIP_FILECOUNT_LIMIT zipfile.ZIP_FILECOUNT_LIMIT = 0 super(BufferedZipFileWriter, self).close() zipfile.ZIP_FILECOUNT_LIMIT = tmp return self._buffer.consume()


Tomé la respuesta de Chris B. y creé una solución completa. Aquí está en caso de que alguien más esté interesado:

import os import threading from zipfile import * import zlib, binascii, struct class ZipEntryWriter(threading.Thread): def __init__(self, zf, zinfo, fileobj): self.zf = zf self.zinfo = zinfo self.fileobj = fileobj zinfo.file_size = 0 zinfo.flag_bits = 0x00 zinfo.header_offset = zf.fp.tell() zf._writecheck(zinfo) zf._didModify = True zinfo.CRC = 0 zinfo.compress_size = compress_size = 0 zf.fp.write(zinfo.FileHeader()) super(ZipEntryWriter, self).__init__() def run(self): zinfo = self.zinfo zf = self.zf file_size = 0 CRC = 0 if zinfo.compress_type == ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while True: buf = self.fileobj.read(1024 * 8) if not buf: self.fileobj.close() break file_size = file_size + len(buf) CRC = binascii.crc32(buf, CRC) if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) zf.fp.write(buf) if cmpr: buf = cmpr.flush() compress_size = compress_size + len(buf) zf.fp.write(buf) zinfo.compress_size = compress_size else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size position = zf.fp.tell() zf.fp.seek(zinfo.header_offset + 14, 0) zf.fp.write(struct.pack("<lLL", zinfo.CRC, zinfo.compress_size, zinfo.file_size)) zf.fp.seek(position, 0) zf.filelist.append(zinfo) zf.NameToInfo[zinfo.filename] = zinfo class EnhZipFile(ZipFile, object): def _current_writer(self): return hasattr(self, ''cur_writer'') and self.cur_writer or None def assert_no_current_writer(self): cur_writer = self._current_writer() if cur_writer and cur_writer.isAlive(): raise ValueError(''An entry is already started for name: %s'' % cur_write.zinfo.filename) def write(self, filename, arcname=None, compress_type=None): self.assert_no_current_writer() super(EnhZipFile, self).write(filename, arcname, compress_type) def writestr(self, zinfo_or_arcname, bytes): self.assert_no_current_writer() super(EnhZipFile, self).writestr(zinfo_or_arcname, bytes) def close(self): self.finish_entry() super(EnhZipFile, self).close() def start_entry(self, zipinfo): """ Start writing a new entry with the specified ZipInfo and return a file like object. Any data written to the file like object is read by a background thread and written directly to the zip file. Make sure to close the returned file object, before closing the zipfile, or the close() would end up hanging indefinitely. Only one entry can be open at any time. If multiple entries need to be written, make sure to call finish_entry() before calling any of these methods: - start_entry - write - writestr It is not necessary to explicitly call finish_entry() before closing zipfile. Example: zf = EnhZipFile(''tmp.zip'', ''w'') w = zf.start_entry(ZipInfo(''t.txt'')) w.write("some text") w.close() zf.close() """ self.assert_no_current_writer() r, w = os.pipe() self.cur_writer = ZipEntryWriter(self, zipinfo, os.fdopen(r, ''r'')) self.cur_writer.start() return os.fdopen(w, ''w'') def finish_entry(self, timeout=None): """ Ensure that the ZipEntry that is currently being written is finished. Joins on any background thread to exit. It is safe to call this method multiple times. """ cur_writer = self._current_writer() if not cur_writer or not cur_writer.isAlive(): return cur_writer.join(timeout) if __name__ == "__main__": zf = EnhZipFile(''c:/tmp/t.zip'', ''w'') import time w = zf.start_entry(ZipInfo(''t.txt'', time.localtime()[:6])) w.write("Line1/n") w.write("Line2/n") w.close() zf.finish_entry() w = zf.start_entry(ZipInfo(''p.txt'', time.localtime()[:6])) w.write("Some text/n") w.close() zf.close()