将类文件对象传递给另一个类文件对象的write()方法

5

我正在尝试从网络上获取一个大文件,并将其直接流式传输到zipfile模块提供的zipfile写入器中,类似于以下代码:

from urllib.request import urlopen
from zipfile import ZipFile

zip_file = ZipFile("/a/certain/local/zip/file.zip","a")
entry = zip_file.open("an.entry","w")
entry.write( urlopen("http://a.certain.file/on?the=web") )

显然,这样做不起作用是因为.write接受一个bytes参数,而不是I/O读取器。但是,由于文件相当大,我不想在压缩之前将整个文件加载到RAM中。简单的解决方案是使用bash(从未真正尝试过,可能有误):
curl -s "http://a.certain.file/on?the=web" | zip -q /a/certain/local/zip/file.zip

但是,在Python脚本中加入一行bash代码既不优雅,也不方便。

另一个解决方案是使用urllib.request.urlretrieve下载文件,然后将路径传递给zipfile.ZipFile.open,但这样我仍然需要等待下载完成,而且还会消耗更多的磁盘I/O资源。

在Python中,是否有一种方法可以直接将下载流传递给zipfile写入器,就像上面的bash管道一样?


你有一些替代方案,比如使用gzopen来创建一个.gz文件。 - Jean-François Fabre
@Jean-FrançoisFabre 我需要一个存档文件,因此如果我想使用gzip,我需要使用tar - busukxuan
你可以使用.read(size)以块的形式获取数据。 - furas
@Jean-FrançoisFabre,gzopen是什么?你是指gzip.open还是其他什么东西? - falsePockets
是的,Gzip.open就是它。 - Jean-François Fabre
1个回答

8
你可以使用 shutil.copyfileobj() 来高效地在文件对象之间复制数据:
from shutil import copyfileobj

with ZipFile("/a/certain/local/zip/file.zip", "w") as zip_file:
    with zip_file.open("an.entry", "w") as entry:
        with urlopen("http://a.certain.file/on?the=web") as response:
            shutil.copyfileobj(response, entry)

这将在源文件对象上使用给定的块大小调用.read(),然后将该块传递给目标文件对象上的.write()方法。
如果您使用的是Python 3.5或更早版本(其中尚不能直接写入ZipFile成员),您唯一的选择是先流式传输到临时文件中:
from shutil import copyfileobj
from tempfile import NamedTemporaryFile

with ZipFile("/a/certain/local/zip/file.zip", "w") as zip_file:
    with NamedTemporaryFile() as cache:
        with urlopen("http://a.certain.file/on?the=web") as response:
            shutil.copyfileobj(response, cache)
            cache.flush()
            zipfile.write('an.entry', cache.name)

使用NamedTemporaryFile()只适用于POSIX系统,在Windows上,您无法再次打开同一文件名,因此您必须使用tempfile.mkstemp()生成的名称,从那里打开文件,然后使用try...finally进行清理。


问题在于zip_file.open()只能用于读取 - "r"。而ZipFile.write()仅接受文件名 :( - furas
@furas:我正在查看Python 3.6版修订版 - Martijn Pieters
@furas:对于Python < 3.6,我担心你需要在中间添加一个NamedTempFile() - Martijn Pieters
幸运的是,我正在使用Python 3.6。仅出于理解的目的,复制到“cache”会阻塞直到下载完成吗? - busukxuan
@busukxuan:是的,copyfileobj()调用在response.read()不再产生数据(并且该数据被写入到cache)之前不会返回。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接