Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/svn/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python itertools.takewhile在生成器函数中-为什么只对其求值一次?_Python_Generator_Itertools - Fatal编程技术网

Python itertools.takewhile在生成器函数中-为什么只对其求值一次?

Python itertools.takewhile在生成器函数中-为什么只对其求值一次?,python,generator,itertools,Python,Generator,Itertools,我有这样一个文本文件: 11 2 3 4 11 111 使用Python2.7,我想将其转换为一个行列表,其中换行符分割内部列表中的项目,空行分割外部列表中的项目。像这样: [["11","2","3","4"],["11"],["111"]] 为此,我编写了一个生成器函数,一旦传递了一个打开的文件对象,就会一次生成一个内部列表: def readParag(fileObj): currentParag = [] for line in fileObj:

我有这样一个文本文件:

11
2
3
4

11

111
使用Python2.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!=“\n”,fileObj)
,从而产生另一个列表。但是没有:我得到了一个
StopIteration
。因此我推测,
take while
表达式在生成器对象创建时只被计算一次,而不是每次调用生成的生成器对象的
next
方法时

这个假设使我想知道如果我再次调用生成器函数会发生什么。结果是,它创建了一个新的生成器对象,该对象还生成了一个结果(预期的第二个结果,即
[“11”]
),然后向我抛出
停止迭代。因此,事实上,将其作为生成器函数有效地给出了相同的结果,就像我将其作为普通函数编写一样,
return
ed列表,而不是
yield
ing列表


我想我可以通过创建自己的类来解决这个问题,而不是使用生成器(如John Millikin对的回答)。但关键是我希望写一些比我原来的生成器函数(甚至可能是生成器表达式)更简洁的东西。有人能告诉我我做错了什么,以及如何纠正吗?

这正是
.takewhile()
应该做的。当条件为true时,它将从底层iterable返回元素,一旦条件为false,它将永久性地切换到迭代完成阶段

请注意,这是迭代器的行为方式;提高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)

这将生成生成器,因此生成的每个项目本身就是一个生成器。您确实需要使用这些生成器中的所有元素才能继续工作;另一个答案中列出的groupby方法也是如此。

您所尝试的是一项完美的工作:

这将提供:

>>> 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]

这是
takewhile
的记录行为。当条件为真时,它需要时间。如果该条件稍后再次变为真,则不会再次启动

简单的解决方法是让函数在循环中只调用takewhile,在takewhile没有更多返回时停止(即,在文件末尾):


您可以多次呼叫takewhile:

>>> def readParagGenerator(fileObj):
...     group = [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
...     while len(group) > 0:
...         yield group
...         group = [ln.rstrip() for ln in takewhile(lambda line: line != "\n", fileObj)]
... 
>>> list(readParagGenerator(StringIO(F)))
[['11', '2', '3', '4'], ['11'], ['111']]

其他答案很好地解释了这里发生的事情,您需要多次调用
takewhile
,而当前生成器没有这样做。下面是一个相当简洁的方法,可以通过使用带有sentinel参数的内置函数来获得所需的行为:

from itertools import takewhile

def readParag(fileObj):
    cond = lambda line: line != "\n"
    return iter(lambda: [ln.rstrip() for ln in takewhile(cond, fileObj)], [])

如果文件内容适合内存,有一种更简单的方法可以用空行分隔组:

with open("filename") as f:
    groups = [group.split() for group in f.read().split("\n\n")]

通过使用
re.split()
而不是
str.split()
以及过滤掉四个或更多连续换行导致的潜在空组,可以使这种方法更加稳健。

。注意,一个可能的修复方法是每次检测到换行时再次调用
takewhile()
。谢谢,Martijn-这很有帮助。你知道有没有一个等价的
.takewhile
不会永久性地切换到“迭代完成”阶段,这样我就可以让我的一行程序按照我的意愿工作了吗?或者我应该继续使用我原来的生成器函数并感谢它完成了任务吗?使用
groupby()
,就像Rik Poggi的答案一样。@JAB:实际上我使用了另一种方法。@JAB Rik Poggi的答案很棒,但除非我遗漏了什么(完全可能!),否则它看起来太复杂,无法作为生成器表达式重新编写,因此,我仍然在想,是否可以使用类似于我的一行程序的方法?使用
bool
而不是
lambda
,并且
生成结果而不是将结果附加到列表中——否则很好!)+1.出于嫉妒。我写了一个版本作为genexp,但没有想到通过剥离行传递groupby,所以我在两个地方使用了
.strip()
,我不喜欢它的外观。你赢了这一轮@DSM,您介意发布您提出的生成器表达式以进行比较吗?@RikPoggi:我认为
g
适用于
list(g)
,并且在一行中的
open
函数调用后缺少一个右括号(即:
[g代表k,g代表groupby((line.strip())代表line in open(“myfile.txt”)),bool)如果k]
)。否则的话,你就有了答案
from itertools import takewhile

def readParag(fileObj):
    cond = lambda line: line != "\n"
    return iter(lambda: [ln.rstrip() for ln in takewhile(cond, fileObj)], [])
with open("filename") as f:
    groups = [group.split() for group in f.read().split("\n\n")]