Python 2.6 不支持向现有压缩包追加内容

5 投票
2 回答
2260 浏览
提问于 2025-04-15 23:14

在我正在开发的一个程序的Python单元测试中,我们使用内存中的zip文件进行端到端测试。在设置阶段(SetUp()),我们创建了一个简单的zip文件,但在某些测试中,我们想要覆盖一些档案。为此,我们使用了“zip.writestr(archive_name, zip.read(archive_name) + new_content)”这样的代码。

import zipfile
from StringIO import StringIO

def Foo():
    zfile = StringIO()
    zip = zipfile.ZipFile(zfile, 'a')
    zip.writestr(
        "foo",
        "foo content")
    zip.writestr(
        "bar",
        "bar content")
    zip.writestr(
        "foo",
        zip.read("foo") +
        "some more foo content")
    print zip.read("bar")

Foo()

问题是,这在Python 2.4和2.5中运行得很好,但在2.6中却不行。在Python 2.6中,这在打印那一行时出错,提示“BadZipfile: 目录中的文件名 'bar' 和头部 'foo' 不同。”

看起来它读取了正确的文件bar,但它却认为应该读取foo。

我感到很困惑。我到底做错了什么?这是不被支持的吗?我试着在网上搜索,但找不到类似问题的任何信息。我也看了zipfile的文档,但没有找到任何我认为相关的内容,尤其是因为我用文件名字符串调用了read()。

有没有什么想法?

提前谢谢你!

2 个回答

0

ZIP文件格式是为了方便添加文件而设计的。你可以添加多个同名的文件,最后提取的时候会取最后一个。但是,ZipFile并不是为了同时读取和写入而设计的。你必须先关闭文件,才能写入结束记录(https://hg.python.org/cpython/file/2.7/Lib/zipfile.py#l1263),然后再通过open()read()方法来读取这些记录(https://hg.python.org/cpython/file/2.7/Lib/zipfile.py#l933)。

import zipfile
from StringIO import StringIO

def Foo():
    zfile = StringIO()

    zip = zipfile.ZipFile(zfile, 'a')
    zip.writestr(
        "foo",
        "foo content")
    zip.writestr(
        "bar",
        "bar content")
    zip.close()

    zip = zipfile.ZipFile(zfile, 'r')
    foo_content = zip.read("foo")

    zip2 = zipfile.ZipFile(zfile, 'a')
    zip2.writestr(
        "foo",
        foo_content +
        "some more foo content")
    print zip2.namelist()
    print zip2.read("bar")

Foo()

输出:

pyzip.py:23: UserWarning: Duplicate name: 'foo'
  "some more foo content")
['foo', 'bar', 'foo']
bar content
2

PKZIP文件的结构非常复杂,简单地在文件末尾添加内容会搞乱这个结构。我不能确定早期版本是否能正常工作,但解决这个问题的方法是:先打开一个zip文件进行读取,再打开一个新的zip文件进行写入,把第一个文件的内容提取出来,然后在最后添加你想加的内容。完成后,用新创建的文件替换掉原来的zip文件。

当我运行你的代码时,出现的错误信息是:

Traceback (most recent call last):
  File "zip.py", line 19, in <module>
    Foo()
  File "zip.py", line 17, in Foo
    print zip.read("bar")
  File "/usr/lib/python2.6/zipfile.py", line 834, in read
    return self.open(name, "r", pwd).read()
  File "/usr/lib/python2.6/zipfile.py", line 874, in open
    zinfo.orig_filename, fname)
zipfile.BadZipfile: File name in directory "bar" and header "foo" differ.

仔细一看,我发现你是从一个以'a'(追加)模式打开的类似文件的StringIO中读取,这会导致读取错误,因为'a'模式通常是不可读的,而且在读写之间必须使用seek()方法调整位置。我会试着调整一下这个问题,并更新代码。

更新:

我几乎是从Doug Hellmann的优秀Python Module of the Week中偷来的这些代码,发现它的工作方式基本符合我的预期。你不能简单地在一个结构化的PKZIP文件中追加内容,如果原帖中的代码曾经有效,那也是偶然的:

import zipfile
import datetime

def create(archive_name):
    print 'creating archive'
    zf = zipfile.ZipFile(archive_name, mode='w')
    try:
        zf.write('/etc/services', arcname='services')
    finally:
        zf.close()

def print_info(archive_name):
    zf = zipfile.ZipFile(archive_name)
    for info in zf.infolist():
        print info.filename
        print '\tComment:\t', info.comment
        print '\tModified:\t', datetime.datetime(*info.date_time)
        print '\tSystem:\t\t', info.create_system, '(0 = Windows, 3 = Unix)'
        print '\tZIP version:\t', info.create_version
        print '\tCompressed:\t', info.compress_size, 'bytes'
        print '\tUncompressed:\t', info.file_size, 'bytes'
        print
    zf.close()

def append(archive_name):
    print 'appending archive'
    zf = zipfile.ZipFile(archive_name, mode='a')
    try:
        zf.write('/etc/hosts', arcname='hosts')
    finally:
        zf.close()

def expand_hosts(archive_name):
    print 'expanding hosts'
    zf = zipfile.ZipFile(archive_name, mode='r')
    try:
        host_contents = zf.read('hosts')
    finally:
        zf.close

    zf =  zipfile.ZipFile(archive_name, mode='a')
    try:
        zf.writestr('hosts', host_contents + '\n# hi mom!')
    finally:
        zf.close()

def main():
    archive = 'zipfile.zip'
    create(archive)
    print_info(archive)
    append(archive)
    print_info(archive)
    expand_hosts(archive)
    print_info(archive)

if __name__ == '__main__': main()

值得注意的是,最后一次调用print_info的输出:

...
hosts
    Modified:   2010-05-20 03:40:24
    Compressed: 404 bytes
    Uncompressed:   404 bytes

hosts
    Modified:   2010-05-27 11:46:28
    Compressed: 414 bytes
    Uncompressed:   414 bytes

它并没有在现有的'hosts'档案名后追加内容,而是创建了一个新的档案成员。

"我之所以把这段话写得更长,是因为我没有时间把它写得更短。"
- 布莱兹·帕斯卡

撰写回答