我有一个奇怪的问题。我有一个格式为的文件:
START
1
2
STOP
lllllllll
START
3
5
6
STOP
我希望将START
和STOP
之间的行视为块,并使用my_f
来处理每个块。
def block_generator(file):
with open(file) as lines:
for line in lines:
if line == 'START':
block=itertools.takewhile(lambda x:x!='STOP',lines)
yield block
在我的主要功能中,我尝试使用map()
来完成工作。它奏效了。
blocks=block_generator(file)
map(my_f,blocks)
实际上会给我我想要的东西。但是当我用multiprocessing.Pool.map()
尝试同样的事情时,它给了我一个错误,说takewhile()想要取2个参数,给出0。
blocks=block_generator(file)
p=multiprocessing.Pool(4)
p.map(my_f,blocks)
这是一个错误吗?
答案 0 :(得分:2)
怎么样:
import itertools
def grouper(n, iterable, fillvalue=None):
# Source: http://docs.python.org/library/itertools.html#recipes
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
return itertools.izip_longest(*[iter(iterable)]*n,fillvalue=fillvalue)
def block_generator(file):
with open(file) as lines:
for line in lines:
if line == 'START':
block=list(itertools.takewhile(lambda x:x!='STOP',lines))
yield block
blocks=block_generator(file)
p=multiprocessing.Pool(4)
for chunk in grouper(100,blocks,fillvalue=''):
p.map(my_f,chunk)
使用grouper
将限制p.map
消耗的文件数量。因此,不需要立即将整个文件读入内存(送入任务队列)。
我在上面声称,当你调用p.map(func,iterator)
时,整个迭代器会被立即消耗以填充任务队列。然后,池工作者从队列中获取任务并同时处理作业。
如果您查看pool.py内部并查看定义,您会看到
_handle_tasks
线程从self._taskqueue
获取项目,并立即枚举:
for i, task in enumerate(taskseq):
...
put(task)
结论是,传递给p.map
的迭代器立刻被消耗掉了。在从队列中获取下一个任务之前,没有等待一个任务结束。
作为进一步的佐证,如果你运行这个:
演示代码:
import multiprocessing as mp
import time
import logging
def foo(x):
time.sleep(1)
return x*x
def blocks():
for x in range(1000):
if x%100==0:
logger.info('Got here')
yield x
logger=mp.log_to_stderr(logging.DEBUG)
logger.setLevel(logging.DEBUG)
pool=mp.Pool()
print pool.map(foo, blocks())
您会看到Got here
消息几乎立即打印10次,然后由于time.sleep(1)
中foo
来电暂停一段时间。这显然表明迭代器在池进程完成任务之前很久就被完全消耗了。
答案 1 :(得分:1)
基本上,当您迭代文件时,每次从文件中读取新行时,都会将文件指针向前移动一行。
所以,当你这样做时
block=itertools.takewhile(lambda x:x!='STOP',lines)
每次takewhile
返回的迭代器都从lines
获取一个新项时,它会移动文件指针。
推进你已经在for
循环中循环的迭代器通常很糟糕。但是,for
循环暂时在每个yield
暂停,map
在takewhile
循环之前耗尽for
,因此您可以获得所需的行为。
当for
循环和takewhile
同时运行时,文件指针会快速移动到最后,然后出错。
请尝试相反,它应该比将takewhile
包裹在list
中更快:
from contextlib import closing
from itertools import repeat
def block_generator(filename):
with open(filename) as infile:
for pos in (infile.tell() for line in infile if line == 'START'):
yield pos
def my_f_wrapper(pos, filename):
with open(filename) as infile:
infile.seek(pos)
block=itertools.takewhile(lambda x:x!='STOP', infile)
my_f(block)
blocks = block_generator(filename)
p.imap(my_f_wrapper, blocks, repeat(filename))
基本上,您希望每个my_f
在文件上独立运行,因此您需要为每个文件单独打开文件。
我想不出一种方法,不需要将文件迭代两次,一次由for
循环,一次由takewhile
全部放在一起,同时仍处理该文件并行。在原始版本中,takewhile
为for
循环提升了文件指针,因此非常有效。
如果你没有迭代线,而只是迭代字节,我建议使用mmap来做这件事,但如果你正在处理文本行,那会使事情变得复杂得多。
修改:另一种方法是让block_generator
浏览该文件,找到START
和STOP
的所有位置,然后将它们成对提供包装器。这样,包装器就不必将行与STOP
进行比较,只需在文件上使用tell()
即可确保它不在STOP
。我不确定这是否会更快。