2011-01-11 16 views
20

Ich habe archive.zip mit zwei Dateien: hello.txt und world.txtÜberschreiben Datei in ZipArchive

I hello.txt Datei durch eine neue mit diesem Code überschrieben werden soll:

aber es wird nicht Datei überschrieben werden soll, irgendwie es erstellt eine weitere Instanz von hello.txt - werfen Sie einen Blick auf winzip Screenshot:

alt text

Da gibt es keine smth wie zipfile.remove(), was ist der beste Weg, um dieses Problem zu behandeln?

+1

offene Frage: https://bugs.python.org/issue6818 – denfromufa

Antwort

26

Es gibt keine Möglichkeit, dies mit Python-Zipfile-Modul zu tun. Sie müssen eine neue Zip-Datei erstellen und alles erneut aus der ersten Datei und der neuen geänderten Datei erneut komprimieren.

Unten ist ein Code, um genau das zu tun. Beachten Sie jedoch, dass es nicht effizient ist, da es alle Daten dekomprimiert und neu komprimiert.

import tempfile 
import zipfile 
import shutil 
import os 

def remove_from_zip(zipfname, *filenames): 
    tempdir = tempfile.mkdtemp() 
    try: 
     tempname = os.path.join(tempdir, 'new.zip') 
     with zipfile.ZipFile(zipfname, 'r') as zipread: 
      with zipfile.ZipFile(tempname, 'w') as zipwrite: 
       for item in zipread.infolist(): 
        if item.filename not in filenames: 
         data = zipread.read(item.filename) 
         zipwrite.writestr(item, data) 
     shutil.move(tempname, zipfname) 
    finally: 
     shutil.rmtree(tempdir) 

Verbrauch:

remove_from_zip('archive.zip', 'hello.txt') 
with zipfile.ZipFile('archive.zip', 'a') as z: 
    z.write('hello.txt') 
+0

So gibt es keine effiziente Methode für Dateien überhaupt zu überschreiben? Vielleicht ein anderes Zip-Modul? Wie auch immer, danke dafür – nukl

+0

@ cru3l: Genau das sage ich in meiner Antwort. – nosklo

+0

Sie können ein externes Zip-Tool aufrufen. Außerdem können Sie Ihre eigene Schnittstelle zu einer Zip-Bibliothek erstellen. – Apalala

10

Aufbauend auf nosklo Antwort. UpdateableZipFile Eine Klasse, die von ZipFile erbt, verwaltet dieselbe Schnittstelle, fügt aber die Möglichkeit hinzu, 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): 
    """ 
    Add delete (via remove_file) and update (via writestr and write methods) 
    To enable update features use UpdateableZipFile with the 'with statement', 
    Upon __exit__ (if updates were applied) a new zip file will override the exiting one with the updates 
    """ 

    class DeleteMarker(object): 
     pass 

    def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): 
     # Init base 
     super(UpdateableZipFile, self).__init__(file, mode=mode, 
               compression=compression, 
               allowZip64=allowZip64) 
     # track file to override in zip 
     self._replace = {} 
     # Whether the with statement was called 
     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 
     # If the file exits, and needs to be overridden, 
     # mark the entry, and create a temp-file for it 
     # we allow this only if the with statement is used 
     if self._allow_updates and name in self.namelist(): 
      temp_file = self._replace[name] = self._replace.get(name, 
                   tempfile.TemporaryFile()) 
      temp_file.write(bytes) 
     # Otherwise just act normally 
     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 
     # If the file exits, and needs to be overridden, 
     # mark the entry, and create a temp-file for it 
     # we allow this only if the with statement is used 
     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) 
     # Otherwise just act normally 
     else: 
      super(UpdateableZipFile, self).write(filename, 
               arcname=arcname, compress_type=compress_type) 

    def __enter__(self): 
     # Allow updates 
     self._allow_updates = True 
     return self 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     # call base to close zip file, organically 
     try: 
      super(UpdateableZipFile, self).__exit__(exc_type, exc_val, exc_tb) 
      if len(self._replace) > 0: 
       self._rebuild_zip() 
     finally: 
      # In case rebuild zip failed, 
      # be sure to still release all the temp files 
      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: 
       # Create new zip with assigned properties 
       with ZipFile(temp_zip_path, 'w', compression=self.compression, 
          allowZip64=self._allowZip64) as zip_write: 
        for item in zip_read.infolist(): 
         # Check if the file should be replaced/or deleted 
         replacement = self._replace.get(item.filename, None) 
         # If marked for deletion, do not copy file to new zipfile 
         if isinstance(replacement, self.DeleteMarker): 
          del self._replace[item.filename] 
          continue 
         # If marked for replacement, copy temp_file, instead of old file 
         elif replacement is not None: 
          del self._replace[item.filename] 
          # Write replacement to archive, 
          # and then close it (deleting the temp file) 
          replacement.seek(0) 
          data = replacement.read() 
          replacement.close() 
         else: 
          data = zip_read.read(item.filename) 
         zip_write.writestr(item, data) 
      # Override the archive with the updated one 
      shutil.move(temp_zip_path, self.filename) 
     finally: 
      shutil.rmtree(tempdir) 

Verwendungsbeispiel:

with UpdateableZipFile("C:\Temp\Test2.docx", "a") as o: 
    # Overwrite a file with a string 
    o.writestr("word/document.xml", "Some data") 
    # exclude an exiting file from the zip 
    o.remove_file("word/fontTable.xml") 
    # Write a new file (with no conflict) to the zp 
    o.writestr("new_file", "more data") 
    # Overwrite a file with a file 
    o.write("C:\Temp\example.png", "word/settings.xml") 
+0

Wenn ich die UpdateableZipFile-Klasse wie in Ihrem Beispiel verwende, werden zwei Ausnahmen ausgelöst: 'File" /home/jezor/Code/updateable_zipfile.py ", Zeile 38, in writestr temp_file.write (Bytes) TypeError: ein Byte- ein ähnliches Objekt wird benötigt, nicht 'str'' und 'File" /home/jezor/Code/updateable_zipfile.py ", Zeile 77, in _close_all_temp_files für Temp_Datei in self._replace.itervalues ​​(): AttributeError:' dict 'Objekt hat keine Attribut 'itervalues'. Könnten Sie bitte Ihre Antwort aktualisieren? Ich benutze Python 3.4 und diese Klasse ist die beste Lösung für [diese Frage] (http://stackoverflow.com/a/37956562/5922757). – Jezor

+1

@ Jesor. Sie versuchen Py2-Code in Py3 auszuführen, daher der Fehler. Ändern Sie 'itervalues' in" values ​​". –

+1

Ich war unsicher über den Code in Anbetracht der geringen Anzahl von Upvotes, aber es funktioniert sehr gut, scheint gut codiert, und entspricht den Anforderungen dieser und ein paar Fragen wiederholen. – VectorVictor