12

このコードは、アップロードされたzipファイルをHTTPマルチパートPOST経由で受信し、内部のデータの読み取り専用処理を行うDjangoアプリのコードを簡略化したものです。

#!/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ファイル内の1つまたは2つのCSVファイルを開きます。

奇妙なことに、これを大きなzipファイル(〜13 MB)で実行し、ZipFilefromStringIO.StringIOまたはa io.BytesIO(おそらくプレーンファイル名以外のもの)をインスタンス化すると、DjangoアプリでZipFilefromを作成しようとしたときに同様の問題が発生しました。および)を呼び出して作成されたTemporaryUploadedFileファイルオブジェクトでさえ、1つではなく2つのcsvファイルを開くと、処理の終わりに向かって失敗します。Linuxシステムで表示される出力は次のとおりです。os.tmpfile()shutil.copyfileobj()

$ ./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.

ちなみに、コードは同じ条件下で失敗しますが、私のOSXシステムでは異なる方法で失敗します。例外の代わりに、BadZipfile破損したデータを読み取るように見え、非常に混乱します。

これはすべて、私がこのコードであなたがしてはいけないことをしていることを私に示唆しています-例えば:zipfile.open同じzipファイルオブジェクト内の別のファイルをすでに開いている間にファイルを呼び出しますか?を使用する場合、これは問題ではないようですが、モジュール内の実装の詳細のために、ファイルのようなオブジェクトZipFile(filename)を渡す場合はおそらく問題がありますか?ZipFilezipfile

おそらく私はzipfileドキュメントで何かを逃しましたか?それともまだ文書化されていませんか?または(最も可能性が低い)、zipfileモジュールのバグですか?

4

4 に答える 4

16

問題と解決策を見つけたばかりかもしれませんが、残念ながら、Pythonのzipfileモジュールを自分のモジュール(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メソッドへのすべての呼び出しに同じ渡されたファイルオブジェクトを使用することです。これは、tellseekが同じファイルで呼び出されることを意味します。したがって、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

デスクトップで開いていたのでしょうか?それは時々私に起こりました、そして、解決策は、Pythonセッションの外でファイルを開かずにコードを実行することでした。

于 2020-09-02T14:54:49.743 に答える