2011-01-11 12 views
20

ho archive.zip con due file: hello.txt e world.txtil file sovrascrivendo in ZipArchive

voglio sovrascrivere hello.txt file con uno nuovo con quel codice:

ma non sovrascrive il file, in qualche modo crea un'altra istanza di hello.txt - dare un'occhiata a WinZip screenshot:

alt text

Poiché non c'è smth come zipfile.remove(), qual è il modo migliore per gestire questo problema?

+1

questione aperta: https://bugs.python.org/issue6818 – denfromufa

risposta

26

Non c'è modo di farlo con il modulo zipfile python. Devi creare un nuovo file zip e ricomprimere di nuovo tutto dal primo file, oltre al nuovo file modificato.

Di seguito è riportato un codice per fare proprio questo. Notare che non è efficiente, poiché decomprime e quindi ricomprime tutti i dati.

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) 

Usage:

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

Quindi, non esiste un metodo efficiente per la sovrascrittura dei file di sorta? Forse un altro modulo zip? Comunque, grazie per questo – nukl

+0

@ cru3l: è esattamente quello che sto dicendo nella mia risposta. – nosklo

+0

È possibile chiamare uno strumento zip esterno. Inoltre, è possibile creare la propria interfaccia per una libreria zip. – Apalala

10

Sulla risposta di nosklo. UpdateableZipFile Una classe che eredita da ZipFile, mantiene la stessa interfaccia ma aggiunge la possibilità di sovrascrivere i file (tramite writestr o scrittura) e rimuovere i file.

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) 

esempio d'uso:

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

Quando utilizzo la classe UpdateableZipFile come nell'esempio, vengono generate due eccezioni: 'File" /home/jezor/Code/updateable_zipfile.py ", riga 38, in writestr temp_file.write (byte) TypeError: a byte- come oggetto è richiesto, non 'str'' e 'File' /home/jezor/Code/updateable_zipfile.py", riga 77, in _close_all_temp_files per temp_file in self._replace.itervalues ​​(): AttributeError: l'oggetto 'dict' non ha attributo 'itervalues''. Potresti aggiornare la tua risposta? Sto usando python 3.4 e quella classe è la soluzione migliore per [questa domanda] (http://stackoverflow.com/a/37956562/5922757). – Jezor

+1

@Jezor. Stai cercando di eseguire il codice Py2 in Py3, quindi l'errore. Cambia 'itervalues' a solo' valori'. –

+1

Non ero sicuro del codice considerando la bassa quantità di upvotes, ma funziona molto bene, sembra ben codificato e soddisfa i requisiti di questo e alcune domande ripetute. – VectorVictor

Problemi correlati