複数の引数のためのPythonマルチプロセッシングpool.map


536

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
驚いたことに、私はこれを行うことpartiallambda行うこともできませんでした。関数がサブプロセスに(を介してpickle)渡される奇妙な方法と関係があると思います。
センダーレ2011年

10
@senderle:これはPython 2.6のバグですが、2.7で修正されました:bugs.python.org/issue5228
unutbu

1
ただ、単純に置き換える pool.map(harvester(text,case),case, 1) ことにより、: pool.apply_async(harvester(text,case),case, 1)
桐グエン

3
@Syrtis_Major、以前に与えられた回答を効果的に歪めるOPの質問を編集しないでください。追加returnharvester()不正確なものになっ@senderieの応答。それは将来の読者を助けません。
Ricalsin 2017年

1
簡単な解決策は、すべての引数をタプルにパックし、それを実行中のfuncにアンパックすることです。プロセスのプールによって実行されているfuncに複雑な複数の引数を送信する必要があるときに、これを行いました。
HS Rathore

回答:


358

これに対する答えは、バージョンと状況に依存します。最近のバージョンのPython(3.3以降)の最も一般的な回答は、JF Sebastianによって最初に説明されました。1Pool.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', ...

より単純なケースでは、2番目の引数を固定して、を使用することもできますが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 2013

ここで最も重要なことは、=RAW_DATASETデフォルト値をに割り当てることですcase。そうしpool.mapないと、複数の引数について混乱します。
Emerson Xu

1
私は混乱していtextます、あなたの例の変数はどうなりましたか?どうやらRAW_DATASET二度合格しているようです。タイプミスがあると思いますか?
デイブ

なぜ使用するとうまくいくのか分かりwith .. as .. ませんがAttributeError: __exit__pool = Pool();手動で呼び出すだけで問題なく動作しますpool.close()(python2.7)
muon

1
@ミュオン、良いキャッチ。表示されたPoolオブジェクトは、Python 3.3までのコンテキストマネージャはなりません。Poolコンテキストマネージャを返す単純なラッパー関数を追加しました。
センダール

501

複数の引数をサポートする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))。もちろん、これは一定数の引数に対してのみ機能しますが、それが彼の唯一のケースである場合、より読みやすくなります。
ビョルンPollex

1
@ Space_C0wb0y:py3kでf((a,b))構文が廃止され、削除されました。そして、ここでは不要です。
jfs

多分もっとpythonic:func = lambda x: func(*x)ラッパー関数を定義する代わりに
dylam

1
@ zthomas.ncこの質問は、マルチプロセッシングpool.mapの複数の引数をサポートする方法についてです。マルチプロセッシングを介して別のPythonプロセスで関数の代わりにメソッドを呼び出す方法を知りたい場合は、別の質問をします(他のすべてが失敗した場合は、常にfunc_star()上記と同様のメソッド呼び出しをラップするグローバル関数を作成できます)
jfs

1
あったらいいのにstarstarmap
КонстантинВан

141

以下が良いと思います

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
最も簡単なソリューション。小さな最適化があります。ラッパー関数を削除し、解凍argsに直接add、それは任意の数の引数のために働く、:def add(args): (x,y) = args
アハメド

1
lambda定義する代わりに関数を使用することもできますmulti_run_wrapper(..)
Andre Holzner

2
hm ...実際には、指定された関数をpickle化しようとするlambdaため、aを使用しても機能しませんpool.map(..)
Andre Holzner

結果をaddリストに保存したい場合、これをどのように使用しますか?
Vivek

@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
これは、2011年の@JFSebastianからの回答(60票以上)とほぼ同じです。
Mike McKerns、2015

29
いいえ、まず最初に、それは多くの不要なものを削除し、それがPython 3.3以降のものであることを明確に述べており、シンプルでクリーンな答えを探している初心者を対象としています。私自身も初心者なので、その方法を理解するのに少し時間がかかりました(はい、JFSebastiansの投稿です)。他の初心者を助けるために私の投稿を書いたのは、彼の投稿が単に「スターマップがある」と説明しましたが、説明しなかったためです-これ私の投稿が意図していることです。ですから、私に2つの反対投票をする理由はまったくありません。
user136036

2011年には、Python 3.3以降に「+」はありませんでした。
Mike McKerns、2015

27

JF Sebastianの回答でitertoolsについて学んだので、私はそれをさらに一歩進め、任意の数の位置引数を取ることができるpython-2.7とpython-3.2(およびそれ以降)でのparmap並列化、提供map、およびstarmap関数を処理するパッケージを作成することにしました。

取り付け

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)

パーマップを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)

20

#「複数の引数を取る方法」。

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

12

multiprocessing呼び出されたpathosのフォーク(注:githubのバージョンを使用)は必要ありません。starmapマップ関数はPythonのマップのAPIを反映しているため、マップは複数の引数を取ることができます。を使用pathosすると、__main__ブロックでスタックする代わりに、通常、インタープリターでマルチプロセッシングを実行することもできます。Pathosは、いくつかの穏やかな更新後のリリースの予定です-ほとんどはpython 3.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,4)ではなく、[pow(1,2,3)、pow(4,5,6)]へのスターマップでアンパックします、pow(2,5)、pow(3、6)]。関数に渡される入力を適切に制御できない場合は、最初にそれらを再構成する必要があります。
スコット

@スコット:ああ、私はそれに気づかなかった... 5年以上前。小さな更新を行います。ありがとう。
Mike McKerns

8

次の2つの関数を使用して、新しい関数ごとにラッパーを作成しないようにすることができます。

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_1そしてarg_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

より良い方法は、手動でラッパー関数記述する代わりに、デコレータを使用することです。特に、マップする関数が多い場合、decoratorはすべての関数のラッパーを記述しないようにすることで時間を節約します。通常、装飾された関数はpickle化できませんが、回避するために使用できます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、10、12、14、16、18]になります。後者を使用する場合は、のitertools.product代わりに使用できますzip
Syrtis Major 2018

4

別の方法は、リストのリストを1つの引数のルーチンに渡すことです。

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 zenに固執すると言えます。それを行うための唯一の明白な方法があるべきです。偶然にもあなたが呼び出し関数の作成者である場合、これはこのメソッドを使用する必要があります。他の場合は、imotaiのメソッドを使用できます。
ニーム、2015年

私の選択はタプルを使用することで、すぐに最初の行の最初のものとしてそれらをアンラップします。
ニーム、2015年

3

IMHOが他のどの回答よりもシンプルでエレガントであることを実現する別の方法を次に示します。

このプログラムには、2つのパラメーターを取り、それらを出力し、合計を出力する関数があります。

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

公式ドキュメントには、反復可能な引数は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フォークで使用される1つの引数の関数に複数の引数を渡すために使用するルーチンの例です。

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なのか。質問の目的に反し
として-場合は
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.