Wes*_*pse 10 python generator python-itertools
我有这样的文本文件:
11
2
3
4
11
111
Run Code Online (Sandbox Code Playgroud)
使用Python 2.7,我想把它变成一个行列表列表,其中换行符分隔内部列表中的项目,空行划分外部列表中的项目.像这样:
[["11","2","3","4"],["11"],["111"]]
Run Code Online (Sandbox Code Playgroud)
为此,我编写了一个生成器函数,一旦传递一个打开的文件对象,就会一次生成一个内部列表:
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 = []
Run Code Online (Sandbox Code Playgroud)
这工作正常,我可以从列表理解中调用它,产生所需的结果.然而,随后我发现我可能能够更简洁地使用相同的东西itertools.takewhile(为了将生成器函数重写为生成器表达式,但我们现在将保留它).这是我试过的:
from itertools import takewhile
def readParag(fileObj):
yield [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
Run Code Online (Sandbox Code Playgroud)
在这种情况下,生成的生成器只产生一个结果(预期的第一个结果,即["11","2","3","4"]).我曾希望next再次调用它的方法会使它takewhile(lambda line: line != "\n", fileObj)再次对文件的其余部分进行评估,从而导致它产生另一个列表.但不是:我得到了一个StopIteration.所以我猜测take while表达式只在生成生成器对象时被评估过一次,而不是每次我都调用生成的生成器对象的next方法.
这个假设让我想知道如果再次调用生成器函数会发生什么.结果是它创建了一个新的生成器对象,["11"]在向StopIteration我投掷之前也产生了单个结果(预期的第二个,即).所以实际上,将其作为生成器函数有效地写入会产生相同的结果,就像我将其作为普通函数编写并return编辑列表而不是编写它一样yield.
我想我可以通过创建自己的类而不是生成器来解决这个问题(如John Millikin对这个问题的回答).但关键是我希望写一些比原始生成器函数更简洁的东西(甚至可能是生成器表达式).有人可以告诉我我做错了什么,以及如何做到对不对?
Rik*_*ggi 26
你要做的是完美的工作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)
Run Code Online (Sandbox Code Playgroud)
这会给:
>>> list(read_parag('myfile.txt')
[['11', '2', '3', '4'], ['11'], ['111']]
Run Code Online (Sandbox Code Playgroud)
或者在一行中:
[list(g) for k,g in groupby((line.strip() for line in open('myfile.txt')), bool) if k]
Run Code Online (Sandbox Code Playgroud)
其他答案很好地解释了这里发生的事情,你需要takewhile多次调用你当前的发电机不能做的事情.下面是一个相当简洁的方法来使用iter()带有sentinel参数的内置函数来获得所需的行为:
from itertools import takewhile
def readParag(fileObj):
cond = lambda line: line != "\n"
return iter(lambda: [ln.rstrip() for ln in takewhile(cond, fileObj)], [])
Run Code Online (Sandbox Code Playgroud)
这正是.takewhile()应该如何表现的.当条件成立时,它将从底层迭代中返回元素,并且一旦它为假,它就会永久地切换到迭代完成阶段.
请注意,这是迭代器必须表现的方式; 提高StopIteration意味着,停止迭代我,我完成了.
表示数据流的对象.重复调用迭代器的
next()方法返回流中的连续项.当没有更多数据可用时,StopIteration会引发异常.此时,迭代器对象已耗尽,并且对其next()方法的任何进一步调用StopIteration再次引发.
您可以结合takewhile使用tee以查看下一批中是否还有其他结果:
import itertools
def readParag(filename):
with open(filename) as f:
while True:
paras = itertools.takewhile(lambda l: l.strip(), f)
test, paras = itertools.tee(paras)
test.next() # raises StopIteration when the file is done
yield (l.strip() for l in paras)
Run Code Online (Sandbox Code Playgroud)
这会产生生成器,因此每个项目产生的本身就是一个生成器.您需要使用这些生成器中的所有元素才能继续工作; 对于另一个答案中列出的groupby方法也是如此.