【问题标题】:Reading file between headers in python在python中的标题之间读取文件
【发布时间】:2018-02-26 15:48:56
【问题描述】:

我有一个大文本文件,其中的值由以“#”开头的标题分隔。如果条件与标题中的条件匹配,我想读取文件直到下一个标题“#”并跳过文件的其余部分。

为了测试我正在尝试读取以下名为 test234.txt 的文本文件:

# abcdefgh
1fnrnf
mrkfr
nfoiernfr
nerfnr
# something
njndjen kj
ejkndjke
#vcrvr

我写的代码是:

file_t = open('test234.txt')
cond = True
while cond:
    for line_ in file_t:
        print(line_)
        if file_t.read(1) == "#":
            cond = False
file_t.close()

但是,我得到的输出是:

# abcdefgh

fnrnf

rkfr

foiernfr

erfnr

something

jndjen kj

jkndjke

vcrvr

相反,我希望两个标题之间的输出由“#”分隔,即:

1fnrnf
mrkfr
nfoiernfr
nerfnr      

我该怎么做?谢谢!

编辑:Reading in file block by block using specified delimiter in python 谈论以标题分隔的组中读取文件,但我不想阅读所有标题。我只想读取满足给定条件的标题,并且一旦该行到达下一个标有“#”的标题,它就会停止读取文件。

【问题讨论】:

  • 该行末尾有一个换行符, print 增加了另一个。使用print(line.rstrip()) 删除尾随的新行..
  • 您的文件是否使用 Windows 行尾 \r\n?如果是这样,请使用rsrip 方法。
  • 是的,文件有 \n 字符,但我只想要“#”指定的 2 个标题之间的输出

标签: python python-3.x


【解决方案1】:

itertools.groupby 可以帮忙:

from io import StringIO
from itertools import groupby

text = '''# abcdefgh
1fnrnf
mrkfr
nfoiernfr
nerfnr
# something
njndjen kj
ejkndjke
#vcrvr'''


with StringIO(text) as file:
    lines = (line.strip() for line in file)  # removing trailing '\n'
    for key, group in groupby(lines, key=lambda x: x[0]=='#'):

        if key is True:
            # found a line that starts with '#'
            print('found header: {}'.format(next(group)))

        if key is False:
            # group now contanins all lines that do not start with '#'
            print('\n'.join(group))

请注意,所有这些都是懒惰的。你只会在内存中拥有两个标题之间的所有项目。

您必须将 with StringIO(text) as file: 替换为; with open('test234.txt', 'r') as file:...

您的测试的输出是:

found header: # abcdefgh
1fnrnf
mrkfr
nfoiernfr
nerfnr
found header: # something
njndjen kj
ejkndjke
found header: #vcrvr

更新,因为我误解了。这是一个新的尝试:

from io import StringIO
from collections import deque
from itertools import takewhile

from_line = '# abcdefgh'
to_line = '# something'

with StringIO(text) as file:
    lines = (line.strip() for line in file)  # removing trailing '\n'

    # fast-forward up to from_line
    deque(takewhile(lambda x: x != from_line, lines), maxlen=0)

    for line in takewhile(lambda x: x != to_line, lines):
        print(line)

我使用itertools.takewhile 在行上获取迭代器,直到满足条件(直到在您的情况下找到第一个标题)。

deque 部分只是 itertools 配方中建议的consume pattern。它只是快进到给定条件不再成立的地步。

【讨论】:

  • 您能解释一下 groupby 是如何工作的吗?组在for循环中逐行迭代吗?此外,由于我尝试阅读的原始文件非常大。那么,代码是逐行读取文件还是一次读取所有行?谢谢!
  • 如前所述:这是懒惰的。这里使用的一切都是生成器。所以是的:文件被逐行处理,而不是作为一个整体读取。 groupby 读取直到条件(行中的第一个字符 == '#'?)改变并返回 key(条件的值)和迭代器 group(这是介于两者之间的所有行)。文档很有帮助。
  • 作为初学者,我花了一些时间来理解您的解决方案。它正在删除标题并将所有数据分组为一个。相反,我只想读取两个给定标题之间的数据并跳过问题中指定的整个文件。也许你已经是这个意思了,但我无法解决。此外,克里斯提出的问题是读取由标题分隔的不同部分中的整个数据,而我只想读取给定标题指定的数据的一部分并跳过其他所有内容。
  • 感谢您的更新。正如@accumulatorax 在其他解决方案中所建议的那样,我还发现使用正则表达式对于作为初学者的理解非常简单。你认为你的建议比使用正则表达式更快更有效吗?我在 accumulatorax 解决方案的基础上开发了自己的解决方案。我可以贴出来对比一下吗?
  • 如果您想了解速度,可以使用timeit 模块。如果你确切地知道你正在寻找的标题是什么样的,我会说正则表达式是矫枉过正的。如果你只知道它的结构,正则表达式就很棒。
【解决方案2】:

学习和使用正则表达式。它将帮助您完成所有文档表示过程。

import re #regex library

with open('test234.txt') as f:  #file stream
    lines = f.readlines()       #reads all lines

p = re.compile('^#.*')          #regex pattern creation

for l in lines:
    if p.match(l) == None:      #looks for non-matching lines
        print(l[:-2])

【讨论】:

  • 能不能给我这样的初学者添加一些cmets来了解更多? re.compile 在那里做什么?
  • 正则表达式逻辑使您可以在字符串中查找模式(由您自己描述)。 ^#.* 表示您正在寻找以# 开头的字符串片段。检查that。了解更多信息。
  • 由于文件很大,如果我不想一次读取所有行,它会起作用吗?
  • 当然你可以用readline()函数在while循环中的“with”缩进中做到这一点
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多