如何在不迭代的情况下读取行?

4

我有一个文本文件,设置了一个条件,需要每隔一行提取一块文本,但是这块文本可以是任意数量的行(对于任何生物信息学人员来说,都是FASTA文件)。它基本上是这样设置的:

> header, info, info
TEXT-------------------------------------------------------
----------------------------------------------------
>header, info...
TEXT-----------------------------------------------------

我正在尝试提取“TEXT”部分。以下是我设置的代码: ```html

...等等。

我正在尝试提取“TEXT”部分。以下是我设置的代码:

```
for line in ffile:
    if line.startswith('>'):

      # do stuff to header line

        try:
            sequence = ""
            seqcheck = ffile.next() # line after the header will always be the beginning of TEXT
            while not seqcheck.startswith('>'):
                        sequence += seqcheck
                        seqcheck = ffile.next()

        except:       # iteration error check
            break

这种方法行不通,因为每次调用next()时,它会继续for循环,导致我跳过了很多行并且丢失了很多数据。我该如何只是“窥视”下一行,而不移动迭代器向前?


1
你为什么要使用那个内部循环呢?if line.startswith(">"): [执行标题相关操作] else: [执行文本相关操作] - tobias_k
5个回答

3

我想,如果你检查一下数据是否以'>'开头,那么会更容易。

>>> content = '''> header, info, info
... TEXT-------------------------------------------------------
... ----------------------------------------------------
... >header, info...
... TEXT-----------------------------------------------------'''
>>> 
>>> f = StringIO(content)
>>> 
>>> my_data = []
>>> for line in f:
...   if not line.startswith('>'):
...     my_data.append(line)
... 
>>> ''.join(my_data)
'TEXT-------------------------------------------------------\n----------------------------------------------------\nTEXT-----------------------------------------------------'
>>> 

更新:

@tobias_k 这应该是分隔行:

>>> def get_content(f):
...   my_data = []
...   for line in f:
...     if line.startswith('>'):
...       yield my_data
...       my_data = []
...     else:
...       my_data.append(line)
...   yield my_data  # the last on
... 
>>> 
>>> f.seek(0)
>>> for i in get_content(f):
...   print i
... 
[]
['TEXT-------------------------------------------------------\n', '----------------------------------------------------\n']
['TEXT-----------------------------------------------------']
>>> 

这将所有文本行放在一起,而不是在标题行处分割它们。 - tobias_k

1
你考虑过正则表达式吗?
txt='''\
> header, info, info
TEXT----------------------------------------------------------------
TEXT2-------------------------------------------
>header, info...
TEXT-----------------------------------------------------'''


import re

for header, data in ((m.group(1), m.group(2)) for m in re.finditer(r'^(?:(>.*?$)(.*?)(?=^>|\Z))', txt, re.S | re.M)):
    # process header
    # process data
    print header, data

查看这个作品

这将会把头信息和数据一起组成一个元组,以便您可以按照需要进行操作。


如果你的文件非常大,你可以使用mmap来避免将整个文件读入内存中。

0

这里有另一种方法。与我上面的评论相反,这个方法确实使用了一个嵌套循环来收集属于一个文本块的所有行(因此这个逻辑不是那么分散),但是做法略有不同:

for line in ffile:
    if not line.startswith('>'):
        sequence = line
        for line in ffile:
            if line.startswith('>'): break
            sequence += line
        print "<text>", sequence
    if line.startswith('>'):
        print "<header>", line

首先,它使用第二个for循环(使用与外部循环相同的ffile迭代器),因此不需要try/except。其次,没有任何行丢失,因为我们将当前的line输入到sequence中,并且因为我们首先处理非标题情况:在第二个if检查被触发时,line变量将保存嵌套循环停止的标题行(不要在这里使用else,否则这将无法工作)。

更新:如果您想将文本行与前面的标题一起处理,可以将标题存储在变量中,例如 lastHeader,并在文本处理中使用它。(类似于FoffT的答案,但是相反的方式,并且仍然具有将文本处理逻辑集中在一个地方的优点。) - tobias_k
那么第二个循环会从第一个循环结束的地方开始吗? - biohax2015
@biohax2015 因为它们使用相同的迭代器,所以是的。但是下一个标题行已经被内部循环消耗了,这就是为什么我正在使用相同的变量名称--“line”--并将标题大小写放在第二位,这样它就会检查内部循环产生的行。 - tobias_k

0

我推荐使用列表和enumerate进行查看:

lines = ffile.readlines()
for i, line in enumerate(lines):
    if line.startswith('>'):
        sequence = ""
        for l in lines[i+1:]:
            if l.startswith('>'):
                break
            sequence += l

0

这里有一个方法,对原始代码的改动非常小。这取决于你的情况,但有时候只需按照自己想要的方式进行操作,并不必担心重新组织/重构其他所有内容!如果你想将某些东西推回去,以便它再次被迭代,那么就只需要让它成为可能!

在这里,我们实例化了一个包含先前读取行的deque()对象。然后,我们包装ffile迭代器,该迭代器对对象进行简单检查,并排除其中的条目,然后从ffile获取新的行。

因此,每当我们读取到需要在其他位置重新处理的内容时,就将其附加到deque对象中并跳出循环。

import cStringIO,collections
original_ffile=cStringIO.StringIO('''
> header, info, info
TEXT----------------------------------------------------------------
TEXT2-------------------------------------------
>header, info...
TEXT-----------------------------------------------------''')

def peaker(_iter,_buffer):
    popleft=_buffer.popleft
    while True:
        while _buffer: yield popleft() # this implements FIFO-style
        yield next(_iter) # we don't have to catch StopIteration here!
buf=collections.deque()
push_back=buf.append
ffile=peaker(original_ffile,buf)
for line in ffile:
    if line.startswith('>'):
        print "found a header! %s"%line[:-1]
        # do stuff to header line
        sequence = ""
        for seqcheck in ffile:
            if seqcheck.startswith('>'):
                print "oops, we've gone too far, pushing back: %s"%seqcheck[:-1]
                push_back(seqcheck)
                break
            sequence += seqcheck

输出:

found a header! > header, info, info
oops, we've gone too far, pushing back: >header, info...
found a header! >header, info...

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