2011-01-11 92 views
20

我有archive.zip有兩個文件:hello.txtworld.txt覆蓋文件中ziparchive

我要覆蓋hello.txt文件與新的與代碼:

,但它不會覆蓋文件,不知何故它創建另一個實例hello.txt - 看看winzip截圖:

alt text

由於沒有像zipfile.remove()那樣的不合適,處理此問題的最佳方法是什麼?

+1

尚未解決的問題:https://bugs.python.org/issue6818 – denfromufa 2016-05-25 16:50:22

回答

26

用python zipfile模塊沒有辦法做到這一點。您必須創建一個新的zip文件並從第一個文件再次壓縮所有內容,再加上新的修改後的文件。

下面是一些代碼來做到這一點。但請注意,它不是有效的,因爲它解壓縮並重新壓縮所有數據。

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) 

用法:

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

所以,對於任何覆蓋文件沒有有效的方法?也許另一個zip模塊?無論如何,謝謝你 – nukl 2011-01-11 03:32:49

+0

@ cru3l:這正是我在回答中所說的。 – nosklo 2011-01-11 03:33:30

10

大廈nosklo的答案。 UpdateableZipFile一個從ZipFile繼承的類,主要是提供相同的接口,但增加了覆蓋文件(通過寫入或寫入)和刪除文件的功能。

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) 

使用示例:

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")