处理两个标记行之间的文本文件行
Processing lines of text file between two marker lines
我的代码处理从文本文件中读取的行(参见末尾的 "Text Processing Details")。我需要修改我的代码,以便它执行相同的任务,但只在某些点之间使用单词。
Code should not bother about this text. Skip it.
*****This is the marker to say where to start working with text. Don't do anything until after these last three asterisks.>***
Work with all of the code in this section
*****Stop working with the text when the first three asterisks are seen*****
Code should not bother about this text. Skip it.
所有情况的标记都是三个星号。只有出现在行首和行尾的标记才算数。
我应该使用什么来让我的代码只在第二组和第三组星号之间工作?
文本处理详情
我的代码读取一个文本文件,将所有单词转为小写,然后拆分单词,将它们放入列表中:
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
我推荐使用正则表达式。
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
您只能使用正则表达式获取星号之间的文本:
import re
betweenAstericks = re.search(r"\*{5}.+?\*{3}(.+?)\*{3}", text, re.DOTALL).group(1)
看起来是一项任务,"count the words between two marker lines",实际上是多项任务。将不同的任务和决策分离到单独的函数和生成器中,这将大大更容易。
第一步:将文件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。 Select 仅标记之间的线。
这真的不止一步。首先,您必须知道什么是标记线,什么不是标记线。这只是一个功能。
然后,您必须前进通过第一个标记(同时丢弃遇到的任何行),最后前进到第二个标记(同时 保持 遇到的任何行)。第二个标记之后的任何内容都不会被读取,更不用说处理了。
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。前进超过第二个标记线,保存内容行。
生成器可以很容易地生成 "start" 标记之后的每一行,但是如果它发现那里 是 没有 "end" 标记,那么就没有办法了返回并取消 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", available as a 30-minute video from PyCon US 2013.
我的代码处理从文本文件中读取的行(参见末尾的 "Text Processing Details")。我需要修改我的代码,以便它执行相同的任务,但只在某些点之间使用单词。
Code should not bother about this text. Skip it.
*****This is the marker to say where to start working with text. Don't do anything until after these last three asterisks.>***
Work with all of the code in this section
*****Stop working with the text when the first three asterisks are seen*****
Code should not bother about this text. Skip it.
所有情况的标记都是三个星号。只有出现在行首和行尾的标记才算数。
我应该使用什么来让我的代码只在第二组和第三组星号之间工作?
文本处理详情
我的代码读取一个文本文件,将所有单词转为小写,然后拆分单词,将它们放入列表中:
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
我推荐使用正则表达式。
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
您只能使用正则表达式获取星号之间的文本:
import re
betweenAstericks = re.search(r"\*{5}.+?\*{3}(.+?)\*{3}", text, re.DOTALL).group(1)
看起来是一项任务,"count the words between two marker lines",实际上是多项任务。将不同的任务和决策分离到单独的函数和生成器中,这将大大更容易。
第一步:将文件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。 Select 仅标记之间的线。
这真的不止一步。首先,您必须知道什么是标记线,什么不是标记线。这只是一个功能。
然后,您必须前进通过第一个标记(同时丢弃遇到的任何行),最后前进到第二个标记(同时 保持 遇到的任何行)。第二个标记之后的任何内容都不会被读取,更不用说处理了。
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。前进超过第二个标记线,保存内容行。
生成器可以很容易地生成 "start" 标记之后的每一行,但是如果它发现那里 是 没有 "end" 标记,那么就没有办法了返回并取消 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", available as a 30-minute video from PyCon US 2013.