【问题标题】:Processing lines of text file between two marker lines处理两个标记行之间的文本文件行
【发布时间】:2016-06-04 16:49:46
【问题描述】:

我的代码处理从文本文件中读取的行(请参阅最后的“文本处理详细信息”)。我需要修改我的代码,以便它执行相同的任务,但只能在某些点之间使用单词。

代码不应该关心这个文本。跳过它。

*****这是指示从何处开始处理文本的标记。在最后三个星号之后不要做任何事情。>***

使用本节中的所有代码

*****看到前三个星号时停止处理文本*****

代码不应该关心这个文本。跳过它。

所有情况的标记都是三个星号。标记仅在出现在行首和行尾时才计算在内。

我应该使用什么来使我的代码只能在第二组和第三组星号之间工作?

文本处理细节

我的代码读取一个文本文件,将所有单词变成小写,然后拆分单词,将它们放入一个列表中:

infile = open(filename, 'r', encoding="utf-8")
text = infile.read().lower().split()

然后它会删除单词中所有语法符号的列表:

list_of_words = [word.strip('\n"-:\';,.') for word in text]

最后,对于该列表中的每个单词,如果它只包含字母符号,它就会被附加到一个新列表中。然后返回该列表:

for word in list_of_words:
    if word.isalpha():
        list_2.append(word)
return list_2

【问题讨论】:

  • 什么是开始或结束标记的定义不是很清楚...有多少个星号构成两个“开始”标记,3 或 5-then-3?他们必须分开吗? (如果输入以连续 8 个星号开头,那是 both 您的“开始”标记,还是只是第一个?)标记仅在出现在开头或结尾时才计数一行,或者它们可以出现在一行中的任何位置?
  • 所有情况的标记都是三个星号。标记仅在出现在行首和行尾时才计算在内。

标签: python file-io text-processing


【解决方案1】:

看似一项任务,“计算两条标记线之间的单词”,实际上是多项任务。将不同的任务和决策分离到单独的函数和生成器中,这将大大更容易。

第 1 步:将文件 I/O 与字数统计分开。为什么字数统计代码要关心单词的来源?

第 2 步:将选择要处理的行与文件处理字数统计分开。为什么字数计数代码应该被赋予它应该计数的字词?对于一个功能来说,这仍然是一项太大的工作,因此将进一步分解。 (这是您要询问的部分。)

第 3 步:处理文本。你已经或多或少地做到了。 (我假设您的文本处理代码最终会出现在一个名为 words 的函数中)。

1。单独的文件 I/O

从文件中读取文本实际上是两个步骤:首先,打开并读取文件,然后从每一行中删除换行符。这是两个工作。

def stripped_lines(lines):
    for line in lines:
        stripped_line = line.rstrip('\n')
        yield stripped_line

def lines_from_file(fname):
    with open(fname, 'rt', encoding='utf8') as flines:
        for line in stripped_lines(flines):
            yield line

这里没有提示您的文本处理。 lines_from_file 生成器只产生在文件中找到的任何字符串......在剥离它们的尾随换行符之后。 (请注意,普通的 strip() 也会删除前导和尾随空格,您必须保留这些空格以识别标记行。)

2。只选择标记之间的线。

这真的不止一步。首先,您必须知道什么是标记线,什么不是标记线。这只是一个功能。

然后,您必须越过第一个标记(同时丢弃遇到的任何线条),最后前进到第二个标记(同时保留遇到的任何线条)。甚至不会读取第二个标记之后的任何内容,更不用说处理了。

Python 的生成器几乎可以为您解决步骤 2 的其余部分。唯一的症结是结束标记...下面的详细信息。

2a。什么是标记线,什么不是标记线?

识别标记线是一个是或否的问题,显然是布尔函数的工作:

def is_marker_line(line, start='***', end='***'):
    '''
    Marker lines start and end with the given strings, which may not
    overlap.  (A line containing just '***' is not a valid marker line.)
    '''
    min_len = len(start) + len(end)
    if len(line) < min_len:
        return False
    return line.startswith(start) and line.endswith(end)

请注意,标记行不需要(根据我对您的要求的阅读)在开始和结束标记之间包含任何文本 --- 六个星号 ('******') 是有效的标记行。

2b。前进超过第一条标记线。

这一步现在很简单:只需丢弃每一行,直到我们找到一条标记线(也将其丢弃)。这个函数不需要担心第二条标记线,或者如果没有没有标记线怎么办,或者别的什么。

def advance_past_next_marker(lines):
    '''
    Advances the given iterator through the first encountered marker
    line, if any.
    '''
    for line in lines:
        if is_marker_line(line):
            break

2c。前进超过第二个标记线,保存内容行。

生成器可以轻松地生成“开始”标记之后的每一行,但如果它发现那里没有“结束”标记,则无法返回并取消yield 这些行.因此,既然您终于遇到了您(可能)真正关心的行,那么您必须将它们全部保存在一个列表中,直到您知道它们是否有效。

def lines_before_next_marker(lines):
    '''
    Yields all lines up to but not including the next marker line.  If
    no marker line is found, yields no lines.
    '''
    valid_lines = []
    for line in lines:
        if is_marker_line(line):
            break
        valid_lines.append(line)
    else:
        # `for` loop did not break, meaning there was no marker line.
        valid_lines = []
    for content_line in valid_lines:
        yield content_line

2d。将第 2 步粘合在一起。

通过第一个标记,然后在第二个标记之前产生所有内容。

def lines_between_markers(lines):
    '''
    Yields the lines between the first two marker lines.
    '''
    # Must use the iterator --- if it's merely an iterable (like a list
    # of strings), the call to lines_before_next_marker will restart
    # from the beginning.
    it = iter(lines)
    advance_past_next_marker(it)
    for line in lines_before_next_marker(it):
        yield line

用一堆输入文件测试这样的函数很烦人。用字符串列表测试它很容易,但列表不是生成器迭代器,它们是可迭代的。额外的it = iter(...) 行是值得的。

3。处理选定的行。

再次,我假设您的文本处理代码安全地封装在一个名为 words 的函数中。唯一的变化是,您不是打开文件并读取它以生成行列表,而是给定行:

def words(lines):
    text = '\n'.join(lines).lower().split()
    # Same as before...

...除了words 也应该是一个生成器。

现在,拨打words 很容易:

def words_from_file(fname):
    for word in words(lines_between_markers(lines_from_file(fname))):
        yield word

要获得words_from_file fname,您需要在lines_between_markers 中找到words,从lines_from_file 中选择...不太英语,但很接近。

4。从您的程序中调用words_from_file

无论您已经在哪里定义了filename --- 大概是在某个地方的main --- 调用words_from_file 一次获得一个字:

filename = ...  # However you defined it before.
for word in words_from_file(filename):
    print(word)

或者,如果您真的需要 list 中的这些词:

filename = ...
word_list = list(words_from_file(filename))

结论

如果试图将其全部压缩到一个或两个函数中,这将是更多困难。这不仅仅是一项任务或决定,而是很多。关键是将其分解为微小的工作,每个工作都易于理解和测试。

生成器摆脱了许多样板代码。如果没有生成器,几乎每个函数都需要一个 for 循环到 some_list.append(next_item),就像在 lines_before_next_marker 中一样。

如果您有 Python 3.3+,yield from ... construct 会删除更多样板文件。每个生成器都包含这样的循环:

for line in stripped_lines(flines):
    yield line

可以改写为:

yield from stripped_lines(flines)

我数了四个。

有关可迭代对象、生成器和使用它们的函数的更多信息,请参阅 Ned Batchelder 的“Loop Like a Native”,提供 30 分钟的 video from PyCon US 2013

【讨论】:

  • 使用此代码,然后将其全部绑定在一个主函数中,如下所示:def main(): word_list = list(words_from_file(fname)) for word in word_list: print(word) main() 返回此错误:"builtins.NameError: name 'fname' is not defined" 关于行此代码:word_list = list(words_from_file(fname))
  • @Hidden 你使用了变量filename,而不是fname...我改了。
  • 我尝试更改变量名称。仍然有同样的错误。
  • @Hidden:我已经编辑了我的答案,以明确这属于您已经拥有的程序。无论您已经定义了filename,请致电words_from_file。 (我以为是main,因为你没有另外说。)
【解决方案2】:

我建议使用正则表达式。

from re import compile, findall

exp = compile(r'\*{5}([^\*]+)\*{3}|"([^"]+)"')

infile = open(filename, 'r', encoding="utf-8")

text = infile.read().lower()  # Notice, no .split()
text_exclusive = ' '.join([''.join(block) for block in findall(exp, text)])

# use text_exclusive from this point forward with your code

【讨论】:

    【解决方案3】:

    您只能使用正则表达式获取星号之间的文本:

    import re
    betweenAstericks = re.search(r"\*{5}.+?\*{3}(.+?)\*{3}", text, re.DOTALL).group(1)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-19
      • 1970-01-01
      • 2011-06-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-14
      • 1970-01-01
      相关资源
      最近更新 更多