12

此代码是 Django 应用程序中代码的简化,该应用程序通过 HTTP 多部分 POST 接收上传的 zip 文件,并对内部数据进行只读处理:

#!/usr/bin/env python

import csv, sys, StringIO, traceback, zipfile
try:
    import io
except ImportError:
    sys.stderr.write('Could not import the `io` module.\n')

def get_zip_file(filename, method):
    if method == 'direct':
        return zipfile.ZipFile(filename)
    elif method == 'StringIO':
        data = file(filename).read()
        return zipfile.ZipFile(StringIO.StringIO(data))
    elif method == 'BytesIO':
        data = file(filename).read()
        return zipfile.ZipFile(io.BytesIO(data))


def process_zip_file(filename, method, open_defaults_file):
    zip_file    = get_zip_file(filename, method)
    items_file  = zip_file.open('items.csv')
    csv_file    = csv.DictReader(items_file)

    try:
        for idx, row in enumerate(csv_file):
            image_filename = row['image1']

            if open_defaults_file:
                z = zip_file.open('defaults.csv')
                z.close()

        sys.stdout.write('Processed %d items.\n' % idx)
    except zipfile.BadZipfile:
        sys.stderr.write('Processing failed on item %d\n\n%s' 
                         % (idx, traceback.format_exc()))


process_zip_file(sys.argv[1], sys.argv[2], int(sys.argv[3]))

很简单。我们打开 zip 文件和 zip 文件中的一两个 CSV 文件。

奇怪的是,如果我用一个大的 zip 文件(~13 MB)运行它并让它实例化ZipFilefrom aStringIO.StringIO或 a io.BytesIO(也许不是普通文件名?我在 Django 应用程序中尝试创建ZipFilefrom时遇到了类似的问题aTemporaryUploadedFile甚至是通过调用os.tmpfile()and shutil.copyfileobj()) 创建的文件对象,并让它打开两个 csv 文件,而不仅仅是一个,然后它在处理结束时失败。这是我在 Linux 系统上看到的输出:

$ ./test_zip_file.py ~/data.zip direct 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip StringIO 1
Processing failed on item 242

Traceback (most recent call last):
  File "./test_zip_file.py", line 26, in process_zip_file
    for idx, row in enumerate(csv_file):
  File ".../python2.7/csv.py", line 104, in next
    row = self.reader.next()
  File ".../python2.7/zipfile.py", line 523, in readline
    return io.BufferedIOBase.readline(self, limit)
  File ".../python2.7/zipfile.py", line 561, in peek
    chunk = self.read(n)
  File ".../python2.7/zipfile.py", line 581, in read
    data = self.read1(n - len(buf))
  File ".../python2.7/zipfile.py", line 641, in read1
    self._update_crc(data, eof=eof)
  File ".../python2.7/zipfile.py", line 596, in _update_crc
    raise BadZipfile("Bad CRC-32 for file %r" % self.name)
BadZipfile: Bad CRC-32 for file 'items.csv'

$ ./test_zip_file.py ~/data.zip BytesIO 1
Processing failed on item 242

Traceback (most recent call last):
  File "./test_zip_file.py", line 26, in process_zip_file
    for idx, row in enumerate(csv_file):
  File ".../python2.7/csv.py", line 104, in next
    row = self.reader.next()
  File ".../python2.7/zipfile.py", line 523, in readline
    return io.BufferedIOBase.readline(self, limit)
  File ".../python2.7/zipfile.py", line 561, in peek
    chunk = self.read(n)
  File ".../python2.7/zipfile.py", line 581, in read
    data = self.read1(n - len(buf))
  File ".../python2.7/zipfile.py", line 641, in read1
    self._update_crc(data, eof=eof)
  File ".../python2.7/zipfile.py", line 596, in _update_crc
    raise BadZipfile("Bad CRC-32 for file %r" % self.name)
BadZipfile: Bad CRC-32 for file 'items.csv'

$ ./test_zip_file.py ~/data.zip StringIO 0
Processed 250 items.

$ ./test_zip_file.py ~/data.zip BytesIO 0
Processed 250 items.

顺便说一句,代码在相同的条件下失败,但在我的 OS X 系统上以不同的方式失败。而不是BadZipfile异常,它似乎读取了损坏的数据并且变得非常困惑。

这一切都向我表明,我在这段代码中做了一些你不应该做的事情——例如:zipfile.open在同一个 zip 文件对象中已经打开另一个文件的同时调用一个文件?使用时这似乎不是问题ZipFile(filename),但在传递ZipFile类似文件的对象时可能会出现问题,因为zipfile模块中的一些实现细节?

也许我错过了zipfile文档中的某些内容?或者它可能还没有记录?或者(最不可能)zipfile模块中的错误?

4

4 回答 4

16

我可能刚刚找到了问题和解决方案,但不幸的是,我不得不用zipfile我自己的一个被黑的模块替换 Python 的模块(myzipfile在这里调用)。

$ diff -u ~/run/lib/python2.7/zipfile.py myzipfile.py
--- /home/msabramo/run/lib/python2.7/zipfile.py 2010-12-22 17:02:34.000000000 -0800
+++ myzipfile.py        2011-04-11 11:51:59.000000000 -0700
@@ -5,6 +5,7 @@
 import binascii, cStringIO, stat
 import io
 import re
+import copy

 try:
     import zlib # We may need its compression method
@@ -877,7 +878,7 @@
         # Only open a new file for instances where we were not
         # given a file object in the constructor
         if self._filePassed:
-            zef_file = self.fp
+            zef_file = copy.copy(self.fp)
         else:
             zef_file = open(self.filename, 'rb')

标准zipfile模块中的问题是,当传递一个文件对象(不是文件名)时,它对每次调用该open方法都使用相同的传入文件对象。这意味着tell并且seek正在同一个文件上被调用,因此试图在 zip 文件中打开多个文件会导致文件位置被共享,因此多次open调用会导致它们相互重叠。相反,当传递一个文件名时,会open打开一个新的文件对象。我的解决方案是针对传入文件对象的情况,而不是直接使用该文件对象,而是创建它的副本。

此更改zipfile解决了我看到的问题:

$ ./test_zip_file.py ~/data.zip StringIO 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip BytesIO 1
Processed 250 items.

$ ./test_zip_file.py ~/data.zip direct 1
Processed 250 items.

但我不知道它是否对zipfile...有其他负面影响

编辑:我刚刚在我之前以某种方式忽略的 Python 文档中发现了这一点。在http://docs.python.org/library/zipfile.html#zipfile.ZipFile.open,它说:

注意:如果 ZipFile 是通过将类文件对象作为第一个参数传递给构造函数来创建的,则返回的对象open()共享 ZipFile 的文件指针。在这种情况下,open()对 ZipFile 对象执行任何附加操作后,不应使用返回的对象。如果 ZipFile 是通过将字符串(文件名)作为第一个参数传递给构造函数来创建的,那么open()将创建一个由 ZipExtFile 保存的新文件对象,允许它独立于 ZipFile 运行。

于 2011-04-11T18:56:09.977 回答
1

我所做的是更新设置工具然后重新下载它现在可以工作了

https://pypi.python.org/pypi/setuptools/35.0.1

于 2017-04-20T03:08:30.880 回答
1

就我而言,这解决了问题:

pip uninstall pillow
于 2019-06-27T16:21:26.970 回答
0

could it be that you had it open in your desktop? It has happened sometimes to me and the solution was just to run the code without having the files open outside of the python session.

于 2020-09-02T14:54:49.743 回答