Python中使用gzip的subprocess

5
我正在尝试通过子进程流式传输数据,将其压缩并写入文件。以下方法可行。我想知道是否可以使用Python的原生gzip库来完成此操作。
fid = gzip.open(self.ipFile, 'rb') # input data
oFid = open(filtSortFile, 'wb') # output file
sort = subprocess.Popen(args="sort | gzip -c ", shell=True, stdin=subprocess.PIPE, stdout=oFid) # set up the pipe
processlines(fid, sort.stdin, filtFid) # pump data into the pipe

问题: 我如何使用python的gzip包来代替这个过程?我想知道为什么下面的方法返回的是文本文件(而不是压缩后的二进制版本)...非常奇怪。
fid = gzip.open(self.ipFile, 'rb')
oFid = gzip.open(filtSortFile, 'wb')
sort = subprocess.Popen(args="sort ", shell=True, stdin=subprocess.PIPE, stdout=oFid)
processlines(fid, sort.stdin, filtFid)
3个回答

6

subprocess 写入 oFid.fileno(),但是gzip 返回底层文件对象的 fd

def fileno(self):
    """Invoke the underlying file object's fileno() method."""
    return self.fileobj.fileno()

为了启用压缩,请直接使用gzip方法:
import gzip
from subprocess import Popen, PIPE
from threading import Thread

def f(input, output):
    for line in iter(input.readline, ''):
        output.write(line)

p = Popen(["sort"], bufsize=-1, stdin=PIPE, stdout=PIPE)
Thread(target=f, args=(p.stdout, gzip.open('out.gz', 'wb'))).start()

for s in "cafebabe":
    p.stdin.write(s+"\n")
p.stdin.close()

示例

$ python gzip_subprocess.py  && od -c out.gz && zcat out.gz 
0000000 037 213  \b  \b 251   E   t   N 002 377   o   u   t  \0   K 344
0000020   J 344   J 002 302   d 256   T       L 343 002  \0   j 017   j
0000040   k 020  \0  \0  \0
0000045
a
a
b
b
c
e
e
f

我喜欢这个解决方案的优雅性。然而,当我测试一个有0.8M行(3.5M压缩)的文件时,这种方法比旧方法多花费了35秒左右的时间。实际上,管道到gzip线程输入的时间与第一种方法完成所需的时间相同。对于一个管道解决方案来说,这似乎有点奇怪? - fodon
bufsize赋值以使用缓冲。 - jfs
你能给一个例子吗?我不确定它如何适用。 - fodon

2

由于您只需指定要提供给正在执行的进程的文件句柄,因此文件对象没有涉及到其他方法。为了解决这个问题,您可以将输出写入管道中,并像下面这样从管道中读取:

oFid = gzip.open(filtSortFile, 'wb')
sort = subprocess.Popen(args="sort ", shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
oFid.writelines(sort.stdout)
oFid.close()

如果被Gzip压缩的流在GB级别,该怎么办?需要一个在deadlines方法调用之前生成数据的过程。在生成数据后且写入行被调用之前,该数据将存储在哪里? - fodon
额,我不太明白你想说什么。 - steabert
你的代码应该可以工作。不过有一个细微差别。我在原始问题中添加了一些代码和注释。如果是这样,你的writelines()方法会在processlines()之后被调用吗?如果是这样,在writelines()被调用之前,这些数据将会放在哪里,一旦它们在processlines()中生成?管道的一个优点是它消除了读/写循环...并且在我的情况下,对于10GB级别的数据变得非常重要。但是我不清楚这个优点是否在这段代码中得以保持...你认为呢? - fodon
我试图回答你的问题,这个问题与你不理解为什么只得到了文本而不是一个gzip文件有关,关键在于你不应该只是将gzip fd传递给子进程。至于你的其他问题:另一个答案已经提供了解决方案(我的示例没有考虑输入管道)。 - steabert

0
是的,可以使用Python的原生gzip库来实现。 我建议查看这个问题:在Python中压缩文件
我现在正在使用Jace Browning的答案
with open('path/to/file', 'rb') as src, gzip.open('path/to/file.gz', 'wb') as dst:
    dst.writelines(src)

虽然有一条评论提到你必须将src内容转换为bytes,但是这段代码并不需要。


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