web-dev-qa-db-ja.com

python concurrent.futures.ProcessPoolExecutor.map()に複数の引数を持つ関数を渡す方法は?

concurrent.futures.ProcessPoolExecutor.map()に2つ以上の引数で構成される関数を呼び出させたいです。以下の例では、lambda関数を使用して、refを同じサイズのnumberlistと同じサイズの配列として定義しています。

最初の質問:これを行うより良い方法はありますか? numberlistのサイズが100万から10億の要素のサイズになる可能性がある場合、refサイズはnumberlistに従う必要があるため、このアプローチは貴重なメモリを不必要に消費します。これは避けたいと思います。 map関数を読み取ると、配列の最短端に到達するまでマッピングが終了するため、これを行いました。

import concurrent.futures as cf

nmax = 10
numberlist = range(nmax)
ref = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5]
workers = 3


def _findmatch(listnumber, ref):    
    print('def _findmatch(listnumber, ref):')
    x=''
    listnumber=str(listnumber)
    ref = str(ref)
    print('listnumber = {0} and ref = {1}'.format(listnumber, ref))
    if ref in listnumber:
        x = listnumber
    print('x = {0}'.format(x))
    return x 

a = map(lambda x, y: _findmatch(x, y), numberlist, ref)
for n in a:
    print(n)
    if str(ref[0]) in n:
        print('match')

with cf.ProcessPoolExecutor(max_workers=workers) as executor:
    #for n in executor.map(_findmatch, numberlist):
    for n in executor.map(lambda x, y: _findmatch(x, ref), numberlist, ref):
        print(type(n))
        print(n)
        if str(ref[0]) in n:
            print('match')

上記のコードを実行したところ、map関数が目的の結果を達成できることがわかりました。ただし、同じ用語をconcurrent.futures.ProcessPoolExecutor.map()に転送すると、python3.5は次のエラーで失敗しました。

Traceback (most recent call last):
  File "/usr/lib/python3.5/multiprocessing/queues.py", line 241, in _feed
    obj = ForkingPickler.dumps(obj)
  File "/usr/lib/python3.5/multiprocessing/reduction.py", line 50, in dumps
    cls(buf, protocol).dump(obj)
_pickle.PicklingError: Can't pickle <function <lambda> at 0x7fd2a14db0d0>: attribute lookup <lambda> on __main__ failed

質問2:このエラーが発生したのはなぜですか。また、concurrent.futures.ProcessPoolExecutor.map()で2つ以上の引数を持つ関数を呼び出すにはどうすればよいですか。

12
Sun Bear

最初に2番目の質問に答えると、使用しているようなlambda関数はpickle化できないため、例外が発生します。 Pythonはpickleプロトコルを使用してメインプロセスとProcessPoolExecutorのワーカープロセス間で渡されるデータをシリアル化するので、これは問題です。理由は明確ではありませんはlambdaをまったく使用しています。元の関数と同じように、使用したラムダは2つの引数を取ります。_findmatchの代わりにlambdaの代わりに直接使用できます。

with cf.ProcessPoolExecutor(max_workers=workers) as executor:
    for n in executor.map(_findmatch, numberlist, ref):
        ...

巨大なリストを作成せずに2番目の定数引数を渡すことに関する最初の問題については、これをいくつかの方法で解決できます。 1つのアプローチはitertools.repeat反復すると、同じ値を永久に繰り返す反復可能なオブジェクトを作成します。

しかし、より良いアプローチは、おそらく定数引数を渡す追加の関数を書くことでしょう。 (おそらく、これがlambda関数を使用しようとした理由ですか?)使用す​​る関数がモジュールのトップレベルの名前空間でアクセス可能であれば機能するはずです。

def _helper(x):
    return _findmatch(x, 5)

with cf.ProcessPoolExecutor(max_workers=workers) as executor:
    for n in executor.map(_helper, numberlist):
        ...
9
Blckknght

(1)リストを作成する必要はありません。 itertools.repeatを使用して、いくつかの値を繰り返すだけのイテレータを作成できます。

(2)実行のためにサブプロセスに渡されるため、名前付き関数をmapに渡す必要があります。 mapは、ピクルプロトコルを使用して物を送信します。ラムダはピクルできないため、マップの一部にすることはできません。しかし、それは完全に不要です。ラムダが行ったのは、2つのパラメーターを持つ2つのパラメーター関数を呼び出すことだけでした。完全に取り外します。

作業コードは

import concurrent.futures as cf
import itertools

nmax = 10
numberlist = range(nmax)
workers = 3

def _findmatch(listnumber, ref):    
    print('def _findmatch(listnumber, ref):')
    x=''
    listnumber=str(listnumber)
    ref = str(ref)
    print('listnumber = {0} and ref = {1}'.format(listnumber, ref))
    if ref in listnumber:
        x = listnumber
    print('x = {0}'.format(x))
    return x 

with cf.ProcessPoolExecutor(max_workers=workers) as executor:
    #for n in executor.map(_findmatch, numberlist):
    for n in executor.map(_findmatch, numberlist, itertools.repeat(5)):
        print(type(n))
        print(n)
        #if str(ref[0]) in n:
        #    print('match')
6
tdelaney

最初の質問について、mapを呼び出したときにのみ値が決定されるが、マップされた関数のすべてのインスタンスに対して定数である引数を渡したいと私は正しく理解していますか?もしそうなら、私はfunctools.partialを使用して2番目の引数(例ではmap)を組み込んだ「テンプレート関数」から派生した関数でrefを実行します。

from functools import partial
refval = 5

def _findmatch(ref, listnumber):  # arguments swapped
    ...

with cf.ProcessPoolExecutor(max_workers=workers) as executor:
    for n in executor.map(partial(_findmatch, refval), numberlist):
        ...

再質問2、最初の部分:関数をピクル(シリアル化)して、並列で実行する必要のある正確なコードを見つけられませんでしたが、それだけではなく、引数だけでなく、関数は何らかの方法でworkersに転送する必要があり、この転送のためにシリアル化する必要がある可能性があります。 partial関数はpickle化できる一方でlambdasはpickle化できないという事実は他の場所で言及されています。たとえば、ここでは https://stackoverflow.com/a/19279016/6356764 です。

再質問2、2番目の部分:ProcessPoolExecutor.mapに複数の引数を指定して関数を呼び出す場合は、最初の引数として関数を渡し、その後に関数の最初の引数のイテラブルを続け、その後に2番目の引数などの反復可能です。あなたの場合:

for n in executor.map(_findmatch, numberlist, ref):
    ...
4
mkorvas