Как избежать использования readlines()?

Мне нужно иметь дело с очень большими текстовыми входными файлами, и я обычно использую .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' очень быстрый.... На чтение строк ушло около 15 минут, а на zcat - всего 1 минута...


person LookIntoEast    schedule 24.08.2011    source источник


Ответы (6)


Если вы можете реорганизовать свой код для линейного чтения файла, то вы можете просто сказать for line in file, чтобы выполнить итерацию по каждой строке файла, не читая все сразу в память. Но, поскольку ваш доступ к файлам выглядит более сложным, вы можете использовать генератор для замены readlines(). Один из способов сделать это — использовать itertools.izip или 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.
person Adam Rosenfield    schedule 24.08.2011

Простым способом было бы,

(псевдокод, может содержать ошибки, только для иллюстрации)

    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()
person Matt Warren    schedule 24.08.2011
comment
Это простой способ, предложение Адама - лучший способ - person Matt Warren; 24.08.2011

Вы можете использовать enumerate для перебора строк в файле, что вернет count и строка на каждой итерации:

with open(file_name) as f:
    for i, line in enumerate(f):
        if i % 4 == 3:
            print i, line
person zeekay    schedule 24.08.2011

Вот как напечатать все строки, содержащие 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
person pts    schedule 24.08.2011

Сложно, потому что на самом деле у вас есть два файла, которые вы обрабатываете одновременно.

Вы можете использовать модуль 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()
person Simon Hibbs    schedule 24.08.2011

Я думаю, что улучшения получения 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 не оценивается.

person eyquem    schedule 24.08.2011