Building on nosklo's answer. UpdateableZipFile Eine Klasse, die von ZipFile erbt, das gleiche Interface beibehält, aber die Möglichkeit bietet, Dateien zu überschreiben (über writestr oder write) und Dateien zu entfernen.
import os
import shutil
import tempfile
from zipfile import ZipFile, ZIP_STORED, ZipInfo
class UpdateableZipFile(ZipFile):
"""
Füge Löschen (über remove_file) und Aktualisieren (über die Methoden writestr und write) hinzu
Um die Aktualisierungsfunktionen zu aktivieren, verwenden Sie UpdateableZipFile mit dem 'with statement',
Bei __exit__ (wenn Aktualisierungen angewendet wurden) wird eine neue Zip-Datei die vorhandene mit den Aktualisierungen überschreiben
"""
class DeleteMarker(object):
pass
def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False):
# Basis initialisieren
super(UpdateableZipFile, self).__init__(file, mode=mode,
compression=compression,
allowZip64=allowZip64)
# Datei zum Überschreiben in zip verfolgen
self._replace = {}
# Ob das with statement aufgerufen wurde
self._allow_updates = False
def writestr(self, zinfo_or_arcname, bytes, compress_type=None):
if isinstance(zinfo_or_arcname, ZipInfo):
name = zinfo_or_arcname.filename
else:
name = zinfo_or_arcname
# Wenn die Datei vorhanden ist und überschrieben werden muss,
# markiere den Eintrag und erstelle eine temp-Datei dafür
# wir erlauben dies nur, wenn das with statement verwendet wird
if self._allow_updates and name in self.namelist():
temp_file = self._replace[name] = self._replace.get(name,
tempfile.TemporaryFile())
temp_file.write(bytes)
# Ansonsten einfach normal verhalten
else:
super(UpdateableZipFile, self).writestr(zinfo_or_arcname,
bytes, compress_type=compress_type)
def write(self, filename, arcname=None, compress_type=None):
arcname = arcname or filename
# Wenn die Datei vorhanden ist und überschrieben werden muss,
# markiere den Eintrag und erstelle eine temp-Datei dafür
# wir erlauben dies nur, wenn das with statement verwendet wird
if self._allow_updates and arcname in self.namelist():
temp_file = self._replace[arcname] = self._replace.get(arcname,
tempfile.TemporaryFile())
with open(filename, "rb") as source:
shutil.copyfileobj(source, temp_file)
# Ansonsten einfach normal verhalten
else:
super(UpdateableZipFile, self).write(filename,
arcname=arcname, compress_type=compress_type)
def __enter__(self):
# Aktualisierungen erlauben
self._allow_updates = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# rufe die Basis auf, um die Zip-Datei organisch zu schließen
try:
super(UpdateableZipFile, self).__exit__(exc_type, exc_val, exc_tb)
if len(self._replace) > 0:
self._rebuild_zip()
finally:
# Falls der Wiederaufbau der Zip fehlschlägt,
# Stellen Sie sicher, dass alle temp-Dateien freigegeben werden
self._close_all_temp_files()
self._allow_updates = False
def _close_all_temp_files(self):
for temp_file in self._replace.itervalues():
if hasattr(temp_file, 'close'):
temp_file.close()
def remove_file(self, path):
self._replace[path] = self.DeleteMarker()
def _rebuild_zip(self):
tempdir = tempfile.mkdtemp()
try:
temp_zip_path = os.path.join(tempdir, 'new.zip')
with ZipFile(self.filename, 'r') as zip_read:
# Erstelle eine neue Zip-Datei mit zugewiesenen Eigenschaften
with ZipFile(temp_zip_path, 'w', compression=self.compression,
allowZip64=self._allowZip64) as zip_write:
for item in zip_read.infolist():
# Überprüfen, ob die Datei ersetzt werden soll oder gelöscht werden soll
replacement = self._replace.get(item.filename, None)
# Wenn Markierung für Löschung, Datei nicht in die neue Zip-Datei kopieren
if isinstance(replacement, self.DeleteMarker):
del self._replace[item.filename]
continue
# Wenn Markierung für den Ersatz, temp-Datei statt der alten Datei kopieren
elif replacement is not None:
del self._replace[item.filename]
# Ersetzung in Archiv schreiben,
# und dann schließen (temp-Datei löschen)
replacement.seek(0)
data = replacement.read()
replacement.close()
else:
data = zip_read.read(item.filename)
zip_write.writestr(item, data)
# Archiv mit der aktualisierten überschreiben
shutil.move(temp_zip_path, self.filename)
finally:
shutil.rmtree(tempdir)
Beispiel für die Verwendung:
with UpdateableZipFile("C:\Temp\Test2.docx", "a") as o:
# Eine Datei mit einem String überschreiben
o.writestr("word/document.xml", "Einige Daten")
# Eine vorhandene Datei vom Zip ausschließen
o.remove_file("word/fontTable.xml")
# Eine neue Datei (ohne Konflikt) in das Zip schreiben
o.writestr("neue_datei", "weitere Daten")
# Eine Datei mit einer Datei überschreiben
o.write(r"C:\Temp\example.png", "word/settings.xml")