内存中的 Python 压缩文件库

是否有一个 Python 库允许在内存中操作压缩文件,而不必使用实际的磁盘文件?

ZipFile 库不允许更新存档。唯一的方法似乎是将其解压缩到一个目录,进行更改,并从该目录创建一个新的 zip。我想修改压缩文件没有磁盘访问,因为我会下载它们,作出更改,并再次上传它们,所以我没有理由存储它们。

类似于 Java 的 ZipInputStream/ZipOutputStream 的东西可以解决这个问题,尽管任何可以避免磁盘访问的接口都可以。

100760 次浏览

来自 In-Memory Zip in Python文章:

下面是我在2008年5月发布的一篇关于 Python 压缩内存的文章,由于 Posterous 关闭而被重新发布。

我最近注意到,Python 中有一个 for-pay 组件可用于在内存中压缩文件。考虑到这应该是免费的,我将以下代码组合在一起。它只经历了非常基本的测试,所以如果有人发现任何错误,请让我知道,我会更新这一点。

import zipfile
import StringIO


class InMemoryZip(object):
def __init__(self):
# Create the in-memory file-like object
self.in_memory_zip = StringIO.StringIO()


def append(self, filename_in_zip, file_contents):
'''Appends a file with name filename_in_zip and contents of
file_contents to the in-memory zip.'''
# Get a handle to the in-memory zip in append mode
zf = zipfile.ZipFile(self.in_memory_zip, "a", zipfile.ZIP_DEFLATED, False)


# Write the file to the in-memory zip
zf.writestr(filename_in_zip, file_contents)


# Mark the files as having been created on Windows so that
# Unix permissions are not inferred as 0000
for zfile in zf.filelist:
zfile.create_system = 0


return self


def read(self):
'''Returns a string with the contents of the in-memory zip.'''
self.in_memory_zip.seek(0)
return self.in_memory_zip.read()


def writetofile(self, filename):
'''Writes the in-memory zip to a file.'''
f = file(filename, "w")
f.write(self.read())
f.close()


if __name__ == "__main__":
# Run a test
imz = InMemoryZip()
imz.append("test.txt", "Another test").append("test2.txt", "Still another")
imz.writetofile("test.zip")

According to the 巨蟒文件:

class zipfile.ZipFile(file[, mode[, compression[, allowZip64]]])


Open a ZIP file, where file can be either a path to a file (a string) or a file-like object.

因此,要打开内存中的文件,只需创建一个类似文件的对象(可能使用 字节输入)。

file_like_object = io.BytesIO(my_zip_data)
zipfile_ob = zipfile.ZipFile(file_like_object)

埃塞尔提供的例子有几个问题,其中一些是主要的:

  • 对 Windows 上的真实数据不起作用。ZIP 文件是二进制的,它的数据应该总是用打开的文件“ wb”写入
  • 为每个文件追加 ZIP 文件,这是低效的。它可以作为 InMemoryZip属性打开和保存
  • 文档指出 ZIP 文件应该显式地关闭,而追加函数则没有这样做(它可能正常工作(例如) ,因为 zf 超出了作用域并关闭了 ZIP 文件)
  • 对于 zipfile 每个中的所有文件,在附加文件时都设置 create _ system 标志,而不是每个文件只附加一次。
  • On Python < 3 cStringIO 比 StringIO 高效得多
  • 不能在 Python 3上工作(最初的文章是在3.0发布之前写的,但是在3.1发布之前,代码已经发布了很长时间)。

如果你安装 ruamel.std.zipfile(我是作者) ,一个更新的版本是可用的

pip install ruamel.std.zipfile

或包括来自 给你的类代码,你可以:

import ruamel.std.zipfile as zipfile


# Run a test
zipfile.InMemoryZipFile()
imz.append("test.txt", "Another test").append("test2.txt", "Still another")
imz.writetofile("test.zip")

您也可以使用 imz.data将内容写到您需要的任何位置。

您还可以使用 with语句,如果您提供了一个文件名,ZIP 的内容将在离开该上下文时写入:

with zipfile.InMemoryZipFile('test.zip') as imz:
imz.append("test.txt", "Another test").append("test2.txt", "Still another")

because of the delayed writing to disc, you can actually read from an old test.zip within that context.

PYTHON 3

import io
import zipfile


zip_buffer = io.BytesIO()


with zipfile.ZipFile(zip_buffer, "a",
zipfile.ZIP_DEFLATED, False) as zip_file:
for file_name, data in [('1.txt', io.BytesIO(b'111')),
('2.txt', io.BytesIO(b'222'))]:
zip_file.writestr(file_name, data.getvalue())


with open('C:/1.zip', 'wb') as f:
f.write(zip_buffer.getvalue())

我想修改压缩文件没有磁盘访问,因为我会下载它们,作出更改,并再次上传它们,所以我没有理由存储它们

这可以使用两个库 https://github.com/uktrade/stream-unziphttps://github.com/uktrade/stream-zip(完全公开: 由我编写)。根据所做的更改,您甚至可能不需要将整个 zip 一次性存储在内存中。

假设您只想下载、解压缩、压缩和重新上传。有点无意义,但你可以插入一些未压缩的内容:

from datetime import datetime
import httpx
from stream_unzip import stream_unzip
from stream_zip import stream_zip, ZIP_64


def get_source_bytes_iter(url):
with httpx.stream('GET', url) as r:
yield from r.iter_bytes()


def get_target_files(files):
# stream-unzip doesn't expose perms or modified_at, but stream-zip requires them
modified_at = datetime.now()
perms = 0o600


for name, _, chunks in files:
# Could change name, manipulate chunks, skip a file, or yield a new file
yield name.decode(), modified_at, perms, ZIP_64, chunks


source_url = 'https://source.test/file.zip'
target_url = 'https://target.test/file.zip'


source_bytes_iter = get_source_bytes_iter(source_url)
source_files = stream_unzip(source_bytes_iter)
target_files = get_target_files(source_files)
target_bytes_iter = stream_zip(target_files)


httpx.put(target_url, data=target_bytes_iter)

我使用 Flask 来创建一个内存中的 zip 文件,并将其作为下载返回。建立在上面来自 Vladimir 的例子之上。seek(0)花了一段时间才弄明白。

import io
import zipfile


zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
for file_name, data in [('1.txt', io.BytesIO(b'111')), ('2.txt', io.BytesIO(b'222'))]:
zip_file.writestr(file_name, data.getvalue())


zip_buffer.seek(0)
return send_file(zip_buffer, attachment_filename='filename.zip', as_attachment=True)