我有一个这样的文本文件:
11
2
3
4
11
111
使用 Python 2.7,我想将其转换为行列表列表,其中换行符分隔内部列表中的项目,空行分隔外部列表中的项目。像这样:
[["11","2","3","4"],["11"],["111"]]
为此,我编写了一个生成器函数,一旦传递了一个打开的文件对象,它就会一次生成一个内部列表:
def readParag(fileObj):
currentParag = []
for line in fileObj:
stripped = line.rstrip()
if len(stripped) > 0: currentParag.append(stripped)
elif len(currentParag) > 0:
yield currentParag
currentParag = []
效果很好,我可以从列表推导式中调用它,从而产生所需的结果。然而,后来我想到我可以使用 itertools.takewhile
更简洁地做同样的事情(为了将生成器函数重写为生成器表达式,但我们将保留它目前)。这是我尝试过的:
from itertools import takewhile
def readParag(fileObj):
yield [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
在这种情况下,生成的生成器只产生一个结果(预期的第一个结果,即 ["11","2","3","4"]
)。我曾希望再次调用它的 next
方法会导致它对文件的其余部分再次计算 takewhile(lambda line: line != "\n", fileObj)
,从而导致它产生另一个列表。但是没有:我得到的是 StopIteration
。所以我推测 take while
表达式只在创建生成器对象时被评估一次,而不是每次我调用结果生成器对象的 next
方法时.
这个假设让我想知道如果我再次调用生成器函数会发生什么。结果是它创建了一个新的生成器对象,该对象在向我抛出 StopIteration
之前也产生了一个结果(预期的第二个结果,即 ["11"]
) .所以事实上,将它写成一个生成器函数会有效地给出相同的结果,就好像我将它写成一个普通函数并return
ed列表而不是yield
ing它一样.
我想我可以通过创建自己的类来代替生成器来解决这个问题(如 John Millikin 对 this question 的回答)。但关键是我希望写一些比我原来的生成器函数(甚至可能是生成器表达式)更简洁的东西。有人可以告诉我我做错了什么,以及如何改正吗?
最佳答案
对于 groupby
,您正在尝试做的是一项完美的工作:
from itertools import groupby
def read_parag(filename):
with open(filename) as f:
for k,g in groupby((line.strip() for line in f), bool):
if k:
yield list(g)
这将给出:
>>> list(read_parag('myfile.txt')
[['11', '2', '3', '4'], ['11'], ['111']]
或者在一行中:
[list(g) for k,g in groupby((line.strip() for line in open('myfile.txt')), bool) if k]
关于python - 生成器函数中的 itertools.takewhile - 为什么只评估一次?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11852579/