python multiprocessing pool.map用于多个参数


532

在Python多处理库中,是否存在pool.map的变体,它支持多个参数?

text = "test"
def harvester(text, case):
    X = case[0]
    text+ str(X)

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=6)
    case = RAW_DATASET
    pool.map(harvester(text,case),case, 1)
    pool.close()
    pool.join()

4
令我惊讶的是,我既做partial也不lambda做。我认为这与将函数传递给子流程(通过pickle)的奇怪方式有关。
senderle 2011年

10
@senderle:这是Python 2.6中的错误,但自2.7起已修复:bugs.python.org/issue5228
unutbu 2011年

1
只需替换 pool.map(harvester(text,case),case, 1) 为: pool.apply_async(harvester(text,case),case, 1)
Tung Nguyen

3
@Syrtis_Major,请不要编辑有效地歪曲先前给出的答案的OP问题。添加returnharvester()原来@senderie的回应应运而生不准确的。这对将来的读者没有帮助。
Ricalsin

1
我会说简单的解决方案是将所有args打包到一个元组中,然后将其解压缩到执行函数中。当我需要将复杂的多个arg发送到由进程池执行的func时,我就这样做了。
拉瑟尔

Answers:


356

答案是取决于版本和情况。JF Sebastian首先描述了最近的Python版本(从3.3开始)的最普遍答案。1它使用Pool.starmap方法,该方法接受一个参数元组序列。然后,它会自动将每个元组的参数解包,并将其传递给给定的函数:

import multiprocessing
from itertools import product

def merge_names(a, b):
    return '{} & {}'.format(a, b)

if __name__ == '__main__':
    names = ['Brown', 'Wilson', 'Bartlett', 'Rivera', 'Molloy', 'Opie']
    with multiprocessing.Pool(processes=3) as pool:
        results = pool.starmap(merge_names, product(names, repeat=2))
    print(results)

# Output: ['Brown & Brown', 'Brown & Wilson', 'Brown & Bartlett', ...

对于早期版本的Python,您需要编写一个辅助函数来显式解压缩参数。如果要使用with,则还需要编写一个包装器以变为Pool上下文管理器。(感谢muon指出这一点。)

import multiprocessing
from itertools import product
from contextlib import contextmanager

def merge_names(a, b):
    return '{} & {}'.format(a, b)

def merge_names_unpack(args):
    return merge_names(*args)

@contextmanager
def poolcontext(*args, **kwargs):
    pool = multiprocessing.Pool(*args, **kwargs)
    yield pool
    pool.terminate()

if __name__ == '__main__':
    names = ['Brown', 'Wilson', 'Bartlett', 'Rivera', 'Molloy', 'Opie']
    with poolcontext(processes=3) as pool:
        results = pool.map(merge_names_unpack, product(names, repeat=2))
    print(results)

# Output: ['Brown & Brown', 'Brown & Wilson', 'Brown & Bartlett', ...

在更简单的情况下,使用固定的第二个参数,您也可以使用partial,但仅在Python 2.7+中使用。

import multiprocessing
from functools import partial
from contextlib import contextmanager

@contextmanager
def poolcontext(*args, **kwargs):
    pool = multiprocessing.Pool(*args, **kwargs)
    yield pool
    pool.terminate()

def merge_names(a, b):
    return '{} & {}'.format(a, b)

if __name__ == '__main__':
    names = ['Brown', 'Wilson', 'Bartlett', 'Rivera', 'Molloy', 'Opie']
    with poolcontext(processes=3) as pool:
        results = pool.map(partial(merge_names, b='Sons'), names)
    print(results)

# Output: ['Brown & Sons', 'Wilson & Sons', 'Bartlett & Sons', ...

1.这在很大程度上是受他的回答启发的,而他的回答可能应该被接受。但是,由于这个问题排在了最前面,所以最好对将来的读者进行改进。


在我看来,RAW_DATASET在这种情况下应该是全局变量?虽然我希望partial_harvester在每个Harvester()调用中更改case的值。如何实现呢?
xgdgsc

这里最重要的是为分配=RAW_DATASET默认值case。否则pool.map会混淆多个参数。
艾默生·许

1
我很困惑,text您的示例中的变量发生了什么?为什么RAW_DATASET看似两次通过。我想你可能有错字?
戴夫

不知道为什么使用with .. as .. 它给我AttributeError: __exit__,但是如果我只是打电话pool = Pool();然后手动关闭pool.close()(python2.7)的话就可以正常工作
muon

1
@muon,好抓。看来Pool对象不成为上下文管理,直到的Python 3.3。我添加了一个简单的包装函数,该函数返回一个Pool上下文管理器。
senderle

499

是否有pool.map的变体,它支持多个参数?

Python 3.3包含pool.starmap()方法

#!/usr/bin/env python3
from functools import partial
from itertools import repeat
from multiprocessing import Pool, freeze_support

def func(a, b):
    return a + b

def main():
    a_args = [1,2,3]
    second_arg = 1
    with Pool() as pool:
        L = pool.starmap(func, [(1, 1), (2, 1), (3, 1)])
        M = pool.starmap(func, zip(a_args, repeat(second_arg)))
        N = pool.map(partial(func, b=second_arg), a_args)
        assert L == M == N

if __name__=="__main__":
    freeze_support()
    main()

对于旧版本:

#!/usr/bin/env python2
import itertools
from multiprocessing import Pool, freeze_support

def func(a, b):
    print a, b

def func_star(a_b):
    """Convert `f([1,2])` to `f(1,2)` call."""
    return func(*a_b)

def main():
    pool = Pool()
    a_args = [1,2,3]
    second_arg = 1
    pool.map(func_star, itertools.izip(a_args, itertools.repeat(second_arg)))

if __name__=="__main__":
    freeze_support()
    main()

输出量

1 1
2 1
3 1

注意此处的用法itertools.izip()itertools.repeat()用法。

由于@unutbu提到的错误,您无法functools.partial()在Python 2.6上使用或类似的功能,因此func_star()应明确定义简单的包装器函数。又见解决方法 的建议uptimebox


1
˚F:你可以在解压的签名参数元组func_star是这样的:def func_star((a, b))。当然,这仅适用于固定数量的参数,但是如果这是他唯一的情况,则更具可读性。
比约恩博动

1
@ Space_C0wb0y:py3k f((a,b))中已弃用并删除了语法。在这里没有必要。
jfs

也许更pythonic:func = lambda x: func(*x)而不是定义一个包装函数
dylam

1
@ zthomas.nc这个问题是关于如何为multiprocessing pool.map支持多个参数。如果想知道如何通过多处理在不同的Python进程中调用方法而不是函数,则提出一个单独的问题(如果其他所有方法均失败,则可以始终创建一个包装上述函数调用的全局函数func_star()
jfs

1
我希望有starstarmap
КонстантинВан

140

我认为下面会更好

def multi_run_wrapper(args):
   return add(*args)
def add(x,y):
    return x+y
if __name__ == "__main__":
    from multiprocessing import Pool
    pool = Pool(4)
    results = pool.map(multi_run_wrapper,[(1,2),(2,3),(3,4)])
    print results

输出

[3, 5, 7]

16
最简单的解决方案。有一个小的优化;删除wrapper函数并args直接在中解压缩add,它适用于任意数量的参数:def add(args): (x,y) = args
Ahmed

1
您也可以使用lambda函数而不是定义multi_run_wrapper(..)
Andre Holzner

2
hm ...实际上,使用a lambda无效,因为pool.map(..)试图使给定的函数腌制
Andre Holzner

如果要将结果存储add在列表中,该如何使用?
Vivek Subramanian

@Ahmed我喜欢它的样子,因为恕我直言,只要参数数量不正确,方法调用就会失败。
Michael Dorner

56

Python 3.3+pool.starmap():

from multiprocessing.dummy import Pool as ThreadPool 

def write(i, x):
    print(i, "---", x)

a = ["1","2","3"]
b = ["4","5","6"] 

pool = ThreadPool(2)
pool.starmap(write, zip(a,b)) 
pool.close() 
pool.join()

结果:

1 --- 4
2 --- 5
3 --- 6

如果您愿意,还可以zip()更多参数: zip(a,b,c,d,e)

如果您希望将常量值作为参数传递,则必须使用import itertools,然后再使用zip(itertools.repeat(constant), a)


2
与@JFSebastian在2011年(含60票以上)的答案一样,这几乎是重复的答案。
Mike McKerns,2015年

29
不会。首先,它删除了很多不必要的内容,并明确指出它适用于python 3.3+,并且适合寻求简单简洁答案的初学者。我本人还是一个初学者,所以花了一些时间弄清楚这种方式(对JFSebastians的帖子是这样),这就是为什么我写我的帖子来帮助其他初学者的原因,因为他的帖子只是说“有星图”,但没有解释-这这就是我的帖子打算的。因此,绝对没有理由用两票来抨击我。
user136036 2015年

在2011年,python 3.3+中没有“ +”……显然。
Mike McKerns,2015年

27

JF Sebastian答案中了解了itertools之后,我决定进一步研究并编写一个parmap程序包,该程序包涉及python-2.7和python-3.2(以及后来的版本)上的并行化,提供mapstarmap功能,可以采用任意数量的位置参数。

安装

pip install parmap

如何并行化:

import parmap
# If you want to do:
y = [myfunction(x, argument1, argument2) for x in mylist]
# In parallel:
y = parmap.map(myfunction, mylist, argument1, argument2)

# If you want to do:
z = [myfunction(x, y, argument1, argument2) for (x,y) in mylist]
# In parallel:
z = parmap.starmap(myfunction, mylist, argument1, argument2)

# If you want to do:
listx = [1, 2, 3, 4, 5, 6]
listy = [2, 3, 4, 5, 6, 7]
param = 3.14
param2 = 42
listz = []
for (x, y) in zip(listx, listy):
        listz.append(myfunction(x, y, param1, param2))
# In parallel:
listz = parmap.starmap(myfunction, zip(listx, listy), param1, param2)

我已将parmap上传到PyPI和github存储库

例如,可以如下回答该问题:

import parmap

def harvester(case, text):
    X = case[0]
    text+ str(X)

if __name__ == "__main__":
    case = RAW_DATASET  # assuming this is an iterable
    parmap.map(harvester, case, "test", chunksize=1)

19

#“如何接受多个参数”。

def f1(args):
    a, b, c = args[0] , args[1] , args[2]
    return a+b+c

if __name__ == "__main__":
    import multiprocessing
    pool = multiprocessing.Pool(4) 

    result1 = pool.map(f1, [ [1,2,3] ])
    print(result1)

2
整洁而优雅。
Prav001

1
我不明白为什么我必须一直滚动到这里才能找到最佳答案。
toti

11

有一个multiprocessing名为pathos的分支(注意:使用github上的版本不需要),因为starmapmap函数镜像了python map的API,因此map可以采用多个参数。使用pathos,您通常还可以在解释器中进行多处理,而不会卡在__main__块中。经过一些轻微的更新后,Pathos即将发布,主要是转换为python3.x。

  Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
  [GCC 4.2.1 (Apple Inc. build 5566)] on darwin
  Type "help", "copyright", "credits" or "license" for more information.
  >>> def func(a,b):
  ...     print a,b
  ...
  >>>
  >>> from pathos.multiprocessing import ProcessingPool    
  >>> pool = ProcessingPool(nodes=4)
  >>> pool.map(func, [1,2,3], [1,1,1])
  1 1
  2 1
  3 1
  [None, None, None]
  >>>
  >>> # also can pickle stuff like lambdas 
  >>> result = pool.map(lambda x: x**2, range(10))
  >>> result
  [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  >>>
  >>> # also does asynchronous map
  >>> result = pool.amap(pow, [1,2,3], [4,5,6])
  >>> result.get()
  [1, 32, 729]
  >>>
  >>> # or can return a map iterator
  >>> result = pool.imap(pow, [1,2,3], [4,5,6])
  >>> result
  <processing.pool.IMapIterator object at 0x110c2ffd0>
  >>> list(result)
  [1, 32, 729]

pathos有几种方法可以使您获得的确切行为starmap

>>> def add(*x):
...   return sum(x)
... 
>>> x = [[1,2,3],[4,5,6]]
>>> import pathos
>>> import numpy as np
>>> # use ProcessPool's map and transposing the inputs
>>> pp = pathos.pools.ProcessPool()
>>> pp.map(add, *np.array(x).T)
[6, 15]
>>> # use ProcessPool's map and a lambda to apply the star
>>> pp.map(lambda x: add(*x), x)
[6, 15]
>>> # use a _ProcessPool, which has starmap
>>> _pp = pathos.pools._ProcessPool()
>>> _pp.starmap(add, x)
[6, 15]
>>> 

我想指出的是,这并未解决原始问题中的结构。[[1,2,3],[4,5,6]]将使用星图解压缩到[pow(1,2,3),pow(4,5,6)],而不是[pow(1,4) ,pow(2,5),pow(3,6)]。如果您无法很好地控制传递给函数的输入,则可能需要首先对其进行重组。
斯科特

@斯科特:啊,我没注意到……五年多以前。我会做一个小更新。谢谢。
Mike McKerns

8

您可以使用以下两个函数,以避免为每个新函数编写包装器:

import itertools
from multiprocessing import Pool

def universal_worker(input_pair):
    function, args = input_pair
    return function(*args)

def pool_args(function, *args):
    return zip(itertools.repeat(function), zip(*args))

使用功能function与参数的清单arg_0arg_1arg_2如下:

pool = Pool(n_core)
list_model = pool.map(universal_worker, pool_args(function, arg_0, arg_1, arg_2)
pool.close()
pool.join()

8

python2的更好解决方案:

from multiprocessing import Pool
def func((i, (a, b))):
    print i, a, b
    return a + b
pool = Pool(3)
pool.map(func, [(0,(1,2)), (1,(2,3)), (2,(3, 4))])

2 3 4

1 2 3

0 1 2

出[]:

[3,5,7]


7

另一个简单的替代方法是将函数参数包装在一个元组中,然后再包装应该在元组中传递的参数。当处理大量数据时,这可能不是理想的。我相信它将为每个元组复制一份。

from multiprocessing import Pool

def f((a,b,c,d)):
    print a,b,c,d
    return a + b + c +d

if __name__ == '__main__':
    p = Pool(10)
    data = [(i+0,i+1,i+2,i+3) for i in xrange(10)]
    print(p.map(f, data))
    p.close()
    p.join()

以某种随机顺序给出输出:

0 1 2 3
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
5 6 7 8
7 8 9 10
6 7 8 9
8 9 10 11
9 10 11 12
[6, 10, 14, 18, 22, 26, 30, 34, 38, 42]

的确如此,仍然在寻找更好的方法:(
法比奥·迪亚斯

6

更好的方法是使用装饰器,而不是手动编写包装器函数。特别是当您有许多要映射的函数时,装饰器将避免为每个函数编写包装器,从而节省了时间。通常,装饰功能是不可选的,但是我们可以使用functools它来解决它。可以在这里找到更多讨论

这里的例子

def unpack_args(func):
    from functools import wraps
    @wraps(func)
    def wrapper(args):
        if isinstance(args, dict):
            return func(**args)
        else:
            return func(*args)
    return wrapper

@unpack_args
def func(x, y):
    return x + y

然后,您可以使用压缩参数映射它

np, xlist, ylist = 2, range(10), range(10)
pool = Pool(np)
res = pool.map(func, zip(xlist, ylist))
pool.close()
pool.join()

当然,您可能总是Pool.starmap在Python 3(> = 3.3)中使用其他答案。


结果与预期不同:[0,2,4,6,8,10,12,14,16,18]我期望:[0,1,2,3,4,5,6,7,8, 9,1,2,3,4,5,6,7,8,9,10,2,3,4,5,6,7,8,9,10,11,...
Tedo Vrbanec

@TedoVrbanec结果应为[0,2,4,6,8,8,10,12,14,16,18]。如果您想要更高版本,可以使用itertools.product代替zip
Syrtis Major

4

另一种方法是将列表的列表传递给一个参数的例程:

import os
from multiprocessing import Pool

def task(args):
    print "PID =", os.getpid(), ", arg1 =", args[0], ", arg2 =", args[1]

pool = Pool()

pool.map(task, [
        [1,2],
        [3,4],
        [5,6],
        [7,8]
    ])

然后,可以使用自己喜欢的方法构造一个参数列表。


这是一种简单的方法,但是您需要更改原始功能。而且,有些时候会回忆起别人无法修改的功能。
WeizhongTu

我会说这坚持Python禅。应该只有一种并且只有一种明显的方式可以做到这一点。如果偶然您是调用函数的作者,则应使用此方法,在其他情况下,我们可以使用imotai的方法。
nehem 2015年

我的选择是使用一个元组,然后立即将它们解包为第一行中的第一件事。
nehem 2015年

3

这是另一种方法,即IMHO比提供的任何其他答案都更加简单和优雅。

该程序具有接受两个参数,将它们打印出来并打印和的功能:

import multiprocessing

def main():

    with multiprocessing.Pool(10) as pool:
        params = [ (2, 2), (3, 3), (4, 4) ]
        pool.starmap(printSum, params)
    # end with

# end function

def printSum(num1, num2):
    mySum = num1 + num2
    print('num1 = ' + str(num1) + ', num2 = ' + str(num2) + ', sum = ' + str(mySum))
# end function

if __name__ == '__main__':
    main()

输出为:

num1 = 2, num2 = 2, sum = 4
num1 = 3, num2 = 3, sum = 6
num1 = 4, num2 = 4, sum = 8

有关更多信息,请参见python文档:

https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool

特别要确保检查出该starmap功能。

我使用的是Python 3.6,我不确定这是否适用于较旧的Python版本

我不确定为什么在文档中没有这样一个非常简单的示例。


2

从python 3.4.4开始,您可以使用multiprocessing.get_context()获得上下文对象以使用多个启动方法:

import multiprocessing as mp

def foo(q, h, w):
    q.put(h + ' ' + w)
    print(h + ' ' + w)

if __name__ == '__main__':
    ctx = mp.get_context('spawn')
    q = ctx.Queue()
    p = ctx.Process(target=foo, args=(q,'hello', 'world'))
    p.start()
    print(q.get())
    p.join()

或者您只需替换

pool.map(harvester(text,case),case, 1)

通过:

pool.apply_async(harvester(text,case),case, 1)

2

这里有很多答案,但似乎没有一个提供可在任何版本上运行的Python 2/3兼容代码。如果您希望您的代码正常工作,则适用于以下任一Python版本:

# For python 2/3 compatibility, define pool context manager
# to support the 'with' statement in Python 2
if sys.version_info[0] == 2:
    from contextlib import contextmanager
    @contextmanager
    def multiprocessing_context(*args, **kwargs):
        pool = multiprocessing.Pool(*args, **kwargs)
        yield pool
        pool.terminate()
else:
    multiprocessing_context = multiprocessing.Pool

之后,您可以随意使用常规Python 3方式进行多处理。例如:

def _function_to_run_for_each(x):
       return x.lower()
with multiprocessing_context(processes=3) as pool:
    results = pool.map(_function_to_run_for_each, ['Bob', 'Sue', 'Tim'])    print(results)

将在Python 2或Python 3中工作。


1

在官方文档中声明它仅支持一个可迭代的参数。在这种情况下,我喜欢使用apply_async。在您的情况下,我会这样做:

from multiprocessing import Process, Pool, Manager

text = "test"
def harvester(text, case, q = None):
 X = case[0]
 res = text+ str(X)
 if q:
  q.put(res)
 return res


def block_until(q, results_queue, until_counter=0):
 i = 0
 while i < until_counter:
  results_queue.put(q.get())
  i+=1

if __name__ == '__main__':
 pool = multiprocessing.Pool(processes=6)
 case = RAW_DATASET
 m = Manager()
 q = m.Queue()
 results_queue = m.Queue() # when it completes results will reside in this queue
 blocking_process = Process(block_until, (q, results_queue, len(case)))
 blocking_process.start()
 for c in case:
  try:
   res = pool.apply_async(harvester, (text, case, q = None))
   res.get(timeout=0.1)
  except:
   pass
 blocking_process.join()

1
text = "test"

def unpack(args):
    return args[0](*args[1:])

def harvester(text, case):
    X = case[0]
    text+ str(X)

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=6)
    case = RAW_DATASET
    # args is a list of tuples 
    # with the function to execute as the first item in each tuple
    args = [(harvester, text, c) for c in case]
    # doing it this way, we can pass any function
    # and we don't need to define a wrapper for each different function
    # if we need to use more than one
    pool.map(unpack, args)
    pool.close()
    pool.join()

1

这是我用来将多个参数传递给pool.imap分支中使用的一个参数函数的例程的示例:

from multiprocessing import Pool

# Wrapper of the function to map:
class makefun:
    def __init__(self, var2):
        self.var2 = var2
    def fun(self, i):
        var2 = self.var2
        return var1[i] + var2

# Couple of variables for the example:
var1 = [1, 2, 3, 5, 6, 7, 8]
var2 = [9, 10, 11, 12]

# Open the pool:
pool = Pool(processes=2)

# Wrapper loop
for j in range(len(var2)):
    # Obtain the function to map
    pool_fun = makefun(var2[j]).fun

    # Fork loop
    for i, value in enumerate(pool.imap(pool_fun, range(len(var1))), 0):
        print(var1[i], '+' ,var2[j], '=', value)

# Close the pool
pool.close()

-3

对于python2,您可以使用此技巧

def fun(a,b):
    return a+b

pool = multiprocessing.Pool(processes=6)
b=233
pool.map(lambda x:fun(x,b),range(1000))

为什么b = 233。否定了问题的目的
如-如果
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.