在我的数据分析中,我通常必须处理各种 ascii 文件(使用空格或逗号分隔值),以及我经常压缩的大文件,尤其是如果它们最终以svn
. 有没有办法编写一个anyOpen()
函数来从文件名中计算出 zip 状态并以适当的方式打开它?
2 回答
是的。例如,将下面的示例放在一个文件中util.py
。它实现了一个解决方案,您可以在其中简单地打开文件(用于读取或写入),例如
f1 = util.anyOpen('data.txt')
f2 = util.anyOpen('data.txt.gz')
f3 = util.anyOpen('data.txt.bz2')
f3 = util.anyOpen('data.txt.xz')
当从命令行读取文件名时,这非常方便。anyOpen()
你不必做任何案例处理,只需传递文件名,
with util.anyOpen(sys.argv[1]) as f:
for line in f:
...
此外,您可以控制解压缩的位置:使用external=NORMAL
python 库,使用external=PROCESS
external gzip
、bzip2
或xz
process;,external=PARALLEL
并行版本pigz
和pbzip2
使用。如果没有找到外部命令,该函数将退回到 python 库(xz
但不是 for )。
添加的功能是通过添加感叹号作为参数的第一个字符filename
(类似于 Mathematica 语法)来打开 unix 管道的简单方法,即
date = util.anyOpen('!date').readline()
或者
ssv_data = util.anyOpen('!cat foo.csv | tr "," " "')
您可能想知道为什么需要外部解压缩过程?有几个原因:(1)现在几乎每个 CPU 都有一个以上的内核,并且外部解压缩过程使您的 Python 代码使用完整内核处理该文件的 I/O。文件的加速尤其明显,.bz2
因为 zip/unzip 非常慢。(2) Pythongzip
和bz
模块不支持解压缩由pigz
和创建的多流文件pbzip2
。(3) 文件不支持 Python .xz
。
NORMAL = 0 # use python zip libraries
PROCESS = 1 # use (zcat, gzip) or (bzcat, bzip2)
PARALLEL = 2 # (pigz -dc, pigz) or (pbzip2 -dc, pbzip2)
def anyOpen(filename, mode='r', buff=1024*1024, external=PARALLEL):
if 'r' in mode and 'w' in mode:
return None
if filename.startswith('!'):
import subprocess
if 'r' in mode:
return subprocess.Popen(filename[1:], shell=True, bufsize=buff,
stdout=subprocess.PIPE).stdout
elif 'w' in mode:
return subprocess.Popen(filename[1:], shell=True, bufsize=buff,
stdin=subprocess.PIPE).stdin
elif filename.endswith('.bz2'):
if external == NORMAL:
import bz2
return bz2.BZ2File(filename, mode, buff)
elif external == PROCESS:
if not which('bzip2'):
return anyOpen(filename, mode, buff, NORMAL)
if 'r' in mode:
return anyOpen('!bzip2 -dc ' + filename, mode, buff)
elif 'w' in mode:
return anyOpen('!bzip2 >' + filename, mode, buff)
elif external == PARALLEL:
if not which('pbzip2'):
return anyOpen(filename, mode, buff, PROCESS)
if 'r' in mode:
return anyOpen('!pbzip2 -dc ' + filename, mode, buff)
elif 'w' in mode:
return anyOpen('!pbzip2 >' + filename, mode, buff)
elif filename.endswith('.gz'):
if external == NORMAL:
import gzip
return gzip.GzipFile(filename, mode, buff)
elif external == PROCESS:
if not which('gzip'):
return anyOpen(filename, mode, buff, NORMAL)
if 'r' in mode:
return anyOpen('!gzip -dc ' + filename, mode, buff)
elif 'w' in mode:
return anyOpen('!gzip >' + filename, mode, buff)
elif external == PARALLEL:
if not which('pigz'):
return anyOpen(filename, mode, buff, PROCESS)
if 'r' in mode:
return anyOpen('!pigz -dc ' + filename, mode, buff)
elif 'w' in mode:
return anyOpen('!pigz >' + filename, mode, buff)
elif filename.endswith('.xz'):
if which('xz'):
if 'r' in mode:
return anyOpen('!xz -dc ' + filename, mode, buff)
elif 'w' in mode:
return anyOpen('!xz >' + filename, mode, buff)
else:
return open(filename, mode, buff)
return None
如果 Python 中存在可执行文件,该which()
函数取自测试吗?例子...
对所有许多案件的清理将是最受欢迎的。快乐的数据monkeying!
我提出了一个比@Darko Vebric 更简单的解决方案。我的解决方案仅使用 python 库,因此不支持多线程解压。如果效率对你很重要,你应该看看他的解决方案。如果您只需要简单和便携的东西,我认为我的解决方案在实现相同目标的同时不那么复杂。
我已经用 Python 3.5 对此进行了测试,但据我所知,总体思路应该适用于任何 Python 版本。当然,您必须检查您的 Python 版本是否支持 lzma 和with
关键字。
import sys
import gzip
import bz2
import lzma
fn = sys.argv[1]
if fn.endswith("gz"):
anyopen = gzip.open
elif fn.endswith("bz2"):
anyopen = bz2.open
elif fn.endswith("xz"):
anyopen = lzma.open
else:
anyopen = open
with anyopen(fn) as f:
for line in f:
# Do something with the lines of the input file