处理两个标记线之间的文本文件行

时间:2016-05-27 02:15:33

标签: python file-io text-processing

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

  

代码不应该打扰这个文本。跳过它。

     

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

     

使用本节中的所有代码

     

*****当看到前三个星号时停止使用文本*****

     

代码不应该打扰这个文本。跳过它。

所有情况的标记都是三个星号。标记仅在它们出现在行的开头和结尾时计算。

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

文本处理详细信息

我的代码读取文本文件,将所有单词设为小写,并将单词拆分,将它们放入列表中:

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 个答案:

答案 0 :(得分:2)

似乎是一项任务,"计算两条标记线之间的字数",实际上是几个。将不同的任务和决策分成单独的函数和生成器,它将更容易

步骤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中。超过第二个标记线,保存内容行。

生成器可以在&#34; start&#34;之后很容易地产生每一行。标记,但如果它发现否&#34;结束&#34;标记,没有办法返回并取消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,您会产生words中找到的lines_between_markers,从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)

我算了其中四个。

有关使用它们的iterables,generator和函数主题的更多信息,请参阅Ned Batchelder&#34; Loop Like a Native&#34;,可在30分钟内使用{{3} }。

答案 1 :(得分:1)

我建议使用正则表达式。

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

答案 2 :(得分:1)

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

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