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つ以上の引数を持つ関数を呼び出すにはどうすればよいですか。
最初に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):
...
(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')
最初の質問について、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化できる一方でlambda
sはpickle化できないという事実は他の場所で言及されています。たとえば、ここでは https://stackoverflow.com/a/19279016/6356764 です。
再質問2、2番目の部分:ProcessPoolExecutor.map
に複数の引数を指定して関数を呼び出す場合は、最初の引数として関数を渡し、その後に関数の最初の引数のイテラブルを続け、その後に2番目の引数などの反復可能です。あなたの場合:
for n in executor.map(_findmatch, numberlist, ref):
...