Je voudrais que concurrent.futures.ProcessPoolExecutor.map()
appelle une fonction composée de 2 arguments ou plus. Dans l'exemple ci-dessous, j'ai utilisé une fonction lambda
et défini ref
comme un tableau de taille égale à numberlist
avec une valeur identique.
1ère question: Existe-t-il une meilleure façon de procéder? Dans le cas où la taille de la liste de numéros peut être de l'ordre de millions à milliards d'éléments, donc la taille de référence devrait suivre la liste de numéros, cette approche prend inutilement une mémoire précieuse, que j'aimerais éviter. Je l'ai fait parce que j'ai lu que la fonction map
mettra fin à son mappage jusqu'à ce que la fin de tableau la plus courte soit atteinte.
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')
En exécutant le code ci-dessus, j'ai constaté que la fonction map
était en mesure d'atteindre le résultat souhaité. Cependant, lorsque j'ai transféré les mêmes termes vers concurrent.futures.ProcessPoolExecutor.map (), python3.5 a échoué avec cette erreur:
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
Question 2: Pourquoi cette erreur s'est-elle produite et comment puis-je obtenir concurrent.futures.ProcessPoolExecutor.map () pour appeler une fonction avec plus d'un argument?
Pour répondre à votre deuxième question en premier, vous obtenez une exception car une fonction lambda
comme celle que vous utilisez n'est pas picklable. Puisque Python utilise le protocole pickle
pour sérialiser les données transmises entre le processus principal et les processus de travail de ProcessPoolExecutor
, c'est un problème. Ce n'est pas clair pourquoi vous utilisent un lambda
du tout. Le lambda que vous aviez prend deux arguments, tout comme la fonction d'origine. Vous pouvez utiliser _findmatch
directement au lieu de lambda
et cela devrait fonctionner.
with cf.ProcessPoolExecutor(max_workers=workers) as executor:
for n in executor.map(_findmatch, numberlist, ref):
...
Quant au premier problème concernant le passage du deuxième argument constant sans créer de liste géante, vous pouvez résoudre ce problème de plusieurs manières. Une approche pourrait consister à utiliser itertools.repeat
pour créer un objet itérable qui répète la même valeur pour toujours lorsqu'il est itéré.
Mais une meilleure approche serait probablement d'écrire une fonction supplémentaire qui passe l'argument constant pour vous. (C'est peut-être pour cela que vous essayez d'utiliser une fonction lambda
?) Cela devrait fonctionner si la fonction que vous utilisez est accessible dans l'espace de noms de niveau supérieur du module:
def _helper(x):
return _findmatch(x, 5)
with cf.ProcessPoolExecutor(max_workers=workers) as executor:
for n in executor.map(_helper, numberlist):
...
(1) Pas besoin de faire une liste. Vous pouvez utiliser itertools.repeat
pour créer un itérateur qui répète simplement une certaine valeur.
(2) Vous devez passer une fonction nommée à map
car elle sera transmise au sous-processus pour exécution. map
utilise le protocole pickle pour envoyer des choses, les lambdas ne peuvent pas être picklés et ne peuvent donc pas faire partie de la carte. Mais c'est totalement inutile. Votre lambda n'a fait qu'appeler une fonction à 2 paramètres avec 2 paramètres. Retirez-le complètement.
Le code de travail est
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')
Concernant votre première question, est-ce que je comprends bien que vous voulez passer un argument dont la valeur est déterminée uniquement au moment où vous appelez map
mais constante pour toutes les instances de la fonction mappée? Si c'est le cas, je ferais le map
avec une fonction dérivée d'une "fonction modèle" avec le deuxième argument (ref
dans votre exemple) incorporé en utilisant functools.partial
:
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):
...
Ré. question 2, première partie: je n'ai pas trouvé le morceau de code exact qui tente de décaper (sérialiser) la fonction qui devrait ensuite être exécutée en parallèle, mais il semble naturel que cela se produise - non seulement les arguments mais aussi la fonction doit être transférée aux travailleurs d'une manière ou d'une autre, et elle doit probablement être sérialisée pour ce transfert. Le fait que les fonctions partial
peuvent être décapées alors que lambda
s ne le sont pas est mentionné ailleurs, par exemple ici: https://stackoverflow.com/a/19279016/6356764 .
Ré. question 2, deuxième partie: si vous vouliez appeler une fonction avec plus d'un argument dans ProcessPoolExecutor.map
, vous lui passeriez la fonction comme premier argument, suivi d'un itérable des premiers arguments de la fonction, suivi d'un itérable de ses seconds arguments, etc. Dans votre cas:
for n in executor.map(_findmatch, numberlist, ref):
...