Python In-Memory-Zip-Bibliothek

74

Gibt es eine Python-Bibliothek, mit der Zip-Archive im Speicher bearbeitet werden können, ohne dass tatsächliche Festplattendateien verwendet werden müssen?

In der ZipFile-Bibliothek können Sie das Archiv nicht aktualisieren. Die einzige Möglichkeit scheint darin zu bestehen, es in ein Verzeichnis zu extrahieren, Änderungen vorzunehmen und eine neue Zip-Datei aus diesem Verzeichnis zu erstellen. Ich möchte Zip-Archive ohne Festplattenzugriff ändern, da ich sie herunterladen, Änderungen vornehmen und erneut hochladen werde, sodass ich keinen Grund habe, sie zu speichern.

Etwas Ähnliches wie Javas ZipInputStream / ZipOutputStream würde den Trick machen, obwohl jede Schnittstelle, die den Festplattenzugriff vermeidet, in Ordnung wäre.

John B.
quelle
In diesem Beitrag habe ich die gleiche Frage beantwortet. stackoverflow.com/questions/60643857/…
Quinten Cabo

Antworten:

86

Laut den Python-Dokumenten :

class zipfile.ZipFile(file[, mode[, compression[, allowZip64]]])

  Open a ZIP file, where file can be either a path to a file (a string) or a file-like object. 

Um die Datei im Speicher zu öffnen, erstellen Sie einfach ein dateiähnliches Objekt (möglicherweise mit BytesIO ).

file_like_object = io.BytesIO(my_zip_data)
zipfile_ob = zipfile.ZipFile(file_like_object)
Jason R. Coombs
quelle
49

Aus dem Artikel In-Memory Zip in Python :

Unten ist ein Beitrag von mir aus dem Mai 2008 über das Zippen im Speicher mit Python, der seit dem Herunterfahren von Posterous erneut veröffentlicht wurde.

Ich habe kürzlich festgestellt, dass es eine kostenpflichtige Komponente gibt, mit der Dateien im Speicher mit Python komprimiert werden können. Da dies etwas ist, das kostenlos sein sollte, habe ich den folgenden Code zusammengestellt. Es wurden nur sehr grundlegende Tests durchgeführt. Wenn also jemand Fehler findet, lassen Sie es mich wissen und ich werde dies aktualisieren.

import zipfile
import StringIO

class InMemoryZip(object):
    def __init__(self):
        # Create the in-memory file-like object
        self.in_memory_zip = StringIO.StringIO()

    def append(self, filename_in_zip, file_contents):
        '''Appends a file with name filename_in_zip and contents of 
        file_contents to the in-memory zip.'''
        # Get a handle to the in-memory zip in append mode
        zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)

        # Write the file to the in-memory zip
        zf.writestr(filename_in_zip, file_contents)

        # Mark the files as having been created on Windows so that
        # Unix permissions are not inferred as 0000
        for zfile in zf.filelist:
            zfile.create_system = 0        

        return self

    def read(self):
        '''Returns a string with the contents of the in-memory zip.'''
        self.in_memory_zip.seek(0)
        return self.in_memory_zip.read()

    def writetofile(self, filename):
        '''Writes the in-memory zip to a file.'''
        f = file(filename, "w")
        f.write(self.read())
        f.close()

if __name__ == "__main__":
    # Run a test
    imz = InMemoryZip()
    imz.append("test.txt", "Another test").append("test2.txt", "Still another")
    imz.writetofile("test.zip")
Justin Ethier
quelle
Nützlicher Link - Dies ist ein gutes Beispiel für die Verwendung des ZipFile-Objekts auf die in Jasons Antwort beschriebene Weise. Vielen Dank
John B
Kein Problem, ich bin froh, dass Sie es nützlich fanden.
Justin Ethier
2
Fassen Sie den Inhalt des Links hier zusammen, wenn er stirbt, und Ihre Antwort
Ivo Flipse
1
@IvoFlipse - Guter Punkt. Ich habe all diesen Inhalt zu diesem Beitrag hinzugefügt, nur für den Fall.
Justin Ethier
2
Funktioniert nicht wirklich unter Windows oder unter Python 3.X, siehe meine Antwort für eine Aktualisierung des Codes.
Anthon
40

PYTHON 3

import io
import zipfile

zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
    for file_name, data in [('1.txt', io.BytesIO(b'111')), ('2.txt', io.BytesIO(b'222'))]:
        zip_file.writestr(file_name, data.getvalue())
with open('C:/1.zip', 'wb') as f:
    f.write(zip_buffer.getvalue())
Vladimir
quelle
1
Link zur Dokumentation. datakann entweder Bytes oder Strings sein und dies funktionierte perfekt unter Ubuntu und Python 3.6
Edgar H
22

Das von Ethier bereitgestellte Beispiel weist mehrere Probleme auf, von denen einige schwerwiegend sind:

  • funktioniert nicht für echte Daten unter Windows. Eine ZIP-Datei ist binär und ihre Daten sollten immer mit einer geöffneten Datei 'wb' geschrieben werden.
  • Die ZIP-Datei wird für jede Datei angehängt. Dies ist ineffizient. Es kann einfach geöffnet und als InMemoryZipAttribut beibehalten werden
  • In der Dokumentation heißt es, dass ZIP-Dateien explizit geschlossen werden sollten. Dies erfolgt nicht in der Append-Funktion (dies funktioniert wahrscheinlich (zum Beispiel), da zf den Gültigkeitsbereich verlässt und die ZIP-Datei geschlossen wird.)
  • Das Flag create_system wird für alle Dateien in der Zip- Datei jedes Mal gesetzt, wenn eine Datei angehängt wird, anstatt nur einmal pro Datei.
  • unter Python <3 ist cStringIO viel effizienter als StringIO
  • funktioniert nicht mit Python 3 (der ursprüngliche Artikel stammt aus der Zeit vor der Veröffentlichung von 3.0, aber als der Code veröffentlicht wurde, war 3.1 schon lange nicht mehr verfügbar).

Eine aktualisierte Version ist verfügbar, wenn Sie installieren ruamel.std.zipfile(von denen ich der Autor bin). Nach

pip install ruamel.std.zipfile

oder den Code für die Klasse einschließlich von hier können Sie tun:

import ruamel.std.zipfile as zipfile

# Run a test
zipfile.InMemoryZipFile()
imz.append("test.txt", "Another test").append("test2.txt", "Still another")
imz.writetofile("test.zip")  

Alternativ können Sie den Inhalt imz.dataan einen beliebigen Ort schreiben .

Sie können die withAnweisung auch verwenden. Wenn Sie einen Dateinamen angeben, wird der Inhalt der ZIP-Datei beim Verlassen dieses Kontexts geschrieben:

with zipfile.InMemoryZipFile('test.zip') as imz:
    imz.append("test.txt", "Another test").append("test2.txt", "Still another")

Aufgrund des verzögerten Schreibens auf eine Disc können Sie test.zipin diesem Kontext tatsächlich von einem alten lesen .

Anthon
quelle
Warum nicht io.BytesIO in Python 2 verwenden?
Boxed
@boxed Kein besonderer Grund, abgesehen davon sollten Sie überprüfen, ob BytesIO unter 2.7 die viel schnellere zugrunde liegende C-Implementierung verwendet und keine Python-Kompatibilitätsschicht ist, die StringIO (anstelle von CStringIO) aufruft
Anthon,
Dies sollte wirklich mindestens das Grundgerüst des Codes enthalten, den Sie zur tatsächlichen Beantwortung der Frage erstellt haben, anstatt nur den Leuten zu sagen, dass sie ein Modul installieren sollen. Wenn nichts anderes, verlinken Sie zumindest auf die Homepage des Moduls.
SilverbackNet