3

我需要处理超大的txt输入文件,我通常使用 .readlines() 来先读取整个文件,然后将其变成一个列表。

我知道这真的很消耗内存并且可能很慢,但我还需要利用 LIST 特性来操作特定的行,如下所示:

#!/usr/bin/python

import os,sys
import glob
import commands
import gzip

path= '/home/xxx/scratch/'
fastqfiles1=glob.glob(path+'*_1.recal.fastq.gz')

for fastqfile1 in fastqfiles1:
    filename = os.path.basename(fastqfile1)
    job_id = filename.split('_')[0]
    fastqfile2 = os.path.join(path+job_id+'_2.recal.fastq.gz') 

    newfastq1 = os.path.join(path+job_id+'_1.fastq.gz') 
    newfastq2 = os.path.join(path+job_id+'_2.fastq.gz') 

    l1= gzip.open(fastqfile1,'r').readlines()
    l2= gzip.open(fastqfile2,'r').readlines()
    f1=[]
    f2=[]
    for i in range(0,len(l1)):
        if i % 4 == 3:
           b1=[ord(x) for x in l1[i]]
           ave1=sum(b1)/float(len(l1[i]))
           b2=[ord(x) for x in str(l2[i])]
           ave2=sum(b2)/float(len(l2[i]))
           if (ave1 >= 20 and ave2>= 20):
              f1.append(l1[i-3])
              f1.append(l1[i-2])
              f1.append(l1[i-1])
              f1.append(l1[i])
              f2.append(l2[i-3])
              f2.append(l2[i-2])
              f2.append(l2[i-1])
              f2.append(l2[i])
    output1=gzip.open(newfastq1,'w')
    output1.writelines(f1)
    output1.close()
    output2=gzip.open(newfastq2,'w')
    output2.writelines(f2)
    output2.close()

一般来说,我会尝试阅读整个文本的第 4 行,但如果第 4 行满足所需条件,我会将这 4 行附加到文本中。那么我可以避免 readlines() 来实现这一点吗?谢谢

编辑:嗨,实际上我自己找到了一个更好的方法:

import commands
 l1=commands.getoutput('zcat ' + fastqfile1).splitlines(True)
 l2=commands.getoutput('zcat ' + fastqfile2).splitlines(True)

我认为 'zcat' 超级快...... readlines 大约需要 15 分钟,而 zcat 只需要 1 分钟......

4

6 回答 6

6

如果您可以重构代码以线性读取文件,那么您可以只说for line in file遍历文件的每一行,而无需一次将其全部读入内存。但是,由于您的文件访问看起来更复杂,您可以使用生成器来替换readlines(). 一种方法是使用itertools.izipor itertools.izip_longest

def four_at_a_time(iterable):
    """Returns an iterator that returns a 4-tuple of objects at a time from the
       given iterable"""
    args = [iter(iterable) * 4]
    return itertools.izip(*args)
...
l1 = four_at_a_time(gzip.open(fastqfile1, 'r'))
l2 = four_at_a_time(gzip.open(fastqfile2, 'r'))
for i, x in enumerate(itertools.izip(l1, l2))
    # x is now a 2-tuple of 4-tuples of lines (one 4-tuple of lines from the first file,
    # and one 4-tuple of lines from the second file).  Process accordingly.
于 2011-08-24T16:08:22.407 回答
2

一个简单的方法是,

(伪代码,可能包含错误,仅用于说明目的)

    a=gzip.open()
    b=gzip.open()

    last_four_a_lines=[]
    last_four_b_lines=[]

    idx=0

    new_a=[]
    new_b=[]

    while True:
      la=a.readline()
      lb=b.readline()
      if (not la) or (not lb):
        break

      if idx % 4==3:
        a_calc=sum([ something ])/len(la)
        b_calc=sum([ something ])/len(lb)
        if a_calc and b_calc:
          for line in last_four_a_lines:
          new_a.append(line)
          for line in last_four_b_lines:
          new_b.append(line)

      last_four_a_lines.append(la)
      del(last_four_a_lines[0])
      last_four_b_lines.append(lb)
      del(last_four_b_lines[0])
      idx+=1
a.close()
b.close()
于 2011-08-24T16:26:47.710 回答
1

您可以使用enumerate来遍历文件中的行,这将在每次迭代时返回一个计数和一行:

with open(file_name) as f:
    for i, line in enumerate(f):
        if i % 4 == 3:
            print i, line
于 2011-08-24T16:05:36.933 回答
1

以下是如何打印包含foo前 3 行的所有行:

f = open(...)
prevlines = []
for line in f:
  prevlines.append(line)
  del prevlines[:-4]
  if 'foo' in line:
    print prevlines

如果您一次读取 2 个文件(行数相同),请执行以下操作:

f1 = open(...)
f2 = open(...)
prevlines1 = []
for line1 in f1:
  prevlines1.append(line1)
  del prevlines1[:-4]
  line2 = f2.readline()
  prevlines2.append(line2)
  del prevlines2[:-4]
  if 'foo' in line1 and 'bar' in line2:
    print prevlines1, prevlines2
于 2011-08-24T16:16:31.260 回答
0

棘手,因为您实际上有两个要同时处理的文件。

您可以使用 fileinput 模块一次有效地解析文件一行。它也可用于解析文件列表,您可以使用块中的 fileinput.nextfile() 方法并行交替浏览多个文件,一次使用每个文件中的一行。

fileinput.lineno() 方法甚至会为您提供当前文件中的当前行号。您可以在循环主体中使用临时列表来跟踪您的 4 行块。

完全未经测试的临时代码,可能是基于对您的代码功能的误解,如下:

f1 = []
f2 = []
for line in fileinput(filename1, filename2):
    if fileinput.filename() = filename1:
        f1.append(line)
    else:
        f2.append(line)
        if fileinput.lineno() % 4 == 3:
            doMyProcesing()
            f1 = []; f2 = []
    fileinput.nextfile()
于 2011-08-24T16:06:33.523 回答
0

我认为提高 l1 和 l2 的获取是不够的:你必须在全球范围内改进你的代码

我提议:

#!/usr/bin/python

import os
import sys
import gzip

path= '/home/xxx/scratch/'

def gen(gfa,gfb):
    try:
        a = (gfa.readline(),gfa.readline(),gfa.readline(),gfa.readline())
        b = (gfb.readline(),gfb.readline(),gfb.readline(),gfb.readline())
        if sum(imap(ord,a[3]))/float(len(a[3])) >= 20 \
           and sum(imap(ord,b[3]))/float(len(b[3])) >= 20:
            yield (a,b)
    except:
        break

for fastqfile1 in glob.glob(path + '*_1.recal.fastq.gz') :
    pji = path + os.path.basename(fastqfile1).split('_')[0] # pji = path + job_id

    gf1= gzip.open(fastqfile1,'r')
    gf2= gzip.open(os.path.join(pji + '_2.recal.fastq.gz'),'r')

    output1=gzip.open(os.path.join(pji + '_1.fastq.gz'),'w')
    output2=gzip.open(os.path.join(pji + '_2.fastq.gz'),'w')

    for lines1,lines2 in gen(gf1,gf2):
        output1.writelines(lines1)
        output2.writelines(lines2)

    output1.close()
    output2.close()

它应该将执行时间减少 30%。纯猜测。

PS:

代码

if sum(imap(ord,a[3]))/float(len(a[3])) >= 20 \
   and sum(imap(ord,b[3]))/float(len(b[3])) >= 20:

执行得更快,而不是

ave1 = sum(imap(ord,a[3]))/float(len(a[3])) 
ave2 = sum(imap(ord,b[3]))/float(len(b[3]))
if ave1 >= 20 and ave2 >=20: 

因为如果ave1不大于 20,则不会评估对象ave2 。

于 2011-08-24T22:16:26.430 回答