Existe-t-il une variante de pool.map dans la bibliothèque de traitement multiple Python, qui prend en charge plusieurs arguments?
text = "test"
def harvester(text, case):
X = case[0]
text+ str(X)
if __== '__main__':
pool = multiprocessing.Pool(processes=6)
case = RAW_DATASET
pool.map(harvester(text,case),case, 1)
pool.close()
pool.join()
La réponse à cette question dépend de la version et de la situation. La réponse la plus générale pour les versions récentes de Python (depuis la version 3.3) a été décrite pour la première fois par J.F. Sebastian .1 Il utilise la méthode Pool.starmap
, qui accepte une séquence de tuples d’arguments. Il décompresse ensuite automatiquement les arguments de chaque tuple et les transmet à la fonction donnée:
import multiprocessing
from itertools import product
def merge_names(a, b):
return '{} & {}'.format(a, b)
if __== '__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', ...
Pour les versions antérieures de Python, vous devez écrire une fonction d'assistance pour décompresser explicitement les arguments. Si vous souhaitez utiliser with
, vous devez également écrire un wrapper pour transformer Pool
en gestionnaire de contexte. (Merci à muon pour l'avoir signalé.)
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 __== '__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', ...
Dans des cas plus simples, avec un second argument fixe, vous pouvez également utiliser partial
, mais uniquement dans 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 __== '__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. Une grande partie de cela a été inspiré par sa réponse, qui aurait probablement dû être acceptée à la place. Mais comme celui-ci est bloqué au sommet, il semblait préférable de l'améliorer pour les futurs lecteurs.
existe-t-il une variante de pool.map prenant en charge plusieurs arguments?
Python 3.3 inclut pool.starmap()
, méthode :
#!/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()
Pour les anciennes versions:
#!/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
Remarquez comment itertools.izip()
et itertools.repeat()
sont utilisés ici.
En raison de le bogue mentionné par @unutbu vous ne pouvez pas utiliser functools.partial()
ou des fonctionnalités similaires sur Python 2.6, la fonction d'encapsulation simple func_star()
doit donc être définie explicitement. Voir aussi la solution de contournementsuggérée par uptimebox
.
Je pense que le dessous sera meilleur
def multi_run_wrapper(args):
return add(*args)
def add(x,y):
return x+y
if __== "__main__":
from multiprocessing import Pool
pool = Pool(4)
results = pool.map(multi_run_wrapper,[(1,2),(2,3),(3,4)])
print results
sortie
[3, 5, 7]
Utilisation de Python 3.3+ avec 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()
Résultat:
1 --- 4
2 --- 5
3 --- 6
Vous pouvez également ajouter plus d'arguments à Zip () si vous aimez: Zip(a,b,c,d,e)
Si vous souhaitez qu'une valeur constante soit passée en argument, vous devez utiliser import itertools
puis Zip(itertools.repeat(constant), a)
par exemple.
Ayant appris l'existence d'itertools dans JF Sebastian answer, j'ai décidé d'aller un peu plus loin et d'écrire un paquetage parmap
qui prend en charge la parallélisation, offrant des fonctions map
et starmap
sur python-2.7 et python-3.2 (et aussi ultérieur) qui peut prendre un nombre quelconque d'arguments de position.
Installation
pip install parmap
Comment paralléliser:
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)
J'ai chargé parmap dans PyPI et dans un référentiel github .
Par exemple, la question peut recevoir la réponse suivante:
import parmap
def harvester(case, text):
X = case[0]
text+ str(X)
if __== "__main__":
case = RAW_DATASET # assuming this is an iterable
parmap.map(harvester, case, "test", chunksize=1)
Il existe une fourchette de multiprocessing
appelée pathos (note: utilisez la version sur github) qui n’a pas besoin de starmap
- les fonctions de la carte reflètent l’API de la carte de python. Cette carte peut donc prendre plusieurs arguments. Avec pathos
, vous pouvez aussi généralement effectuer un multitraitement dans l’interpréteur, au lieu d’être bloqué dans le bloc __main__
. Pathos doit être publié, après quelques mises à jour mineures - principalement une conversion en 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]
Vous pouvez utiliser les deux fonctions suivantes pour éviter d'écrire un wrapper pour chaque nouvelle fonction:
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))
Utilisez la fonction function
avec les listes d'arguments arg_0
, arg_1
et arg_2
comme suit:
pool = Pool(n_core)
list_model = pool.map(universal_worker, pool_args(function, arg_0, arg_1, arg_2)
pool.close()
pool.join()
Une meilleure solution pour 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
en dehors[]:
[3, 5, 7]
Une autre solution simple consiste à envelopper les paramètres de votre fonction dans un tuple, puis à envelopper les paramètres qui doivent également être transmis en tuples. Ce n'est peut-être pas idéal lorsqu'il s'agit de traiter des données volumineuses. Je crois que cela ferait des copies pour chaque tuple.
from multiprocessing import Pool
def f((a,b,c,d)):
print a,b,c,d
return a + b + c +d
if __== '__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()
Donne la sortie dans un ordre aléatoire:
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]
Une meilleure façon consiste à utiliser decorator au lieu d’écrire manuellement wrapper function. Surtout quand vous avez beaucoup de fonctions à mapper, décorateur vous fera gagner du temps en évitant d’écrire des enveloppes pour chaque fonction. Généralement, une fonction décorée n'est pas décapable, mais nous pouvons utiliser functools
pour la contourner. Plus de disscusions peuvent être trouvés ici .
Ici l'exemple
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
Ensuite, vous pouvez le mapper avec des arguments compressés
np, xlist, ylist = 2, range(10), range(10)
pool = Pool(np)
res = pool.map(func, Zip(xlist, ylist))
pool.close()
pool.join()
Bien sûr, vous pouvez toujours utiliser Pool.starmap
en Python 3 (> = 3.3) comme indiqué dans d'autres réponses.
def f1(args):
a, b, c = args[0] , args[1] , args[2]
return a+b+c
if __== "__main__":
import multiprocessing
pool = multiprocessing.Pool(4)
result1 = pool.map(f1, [ [1,2,3] ])
print(result1)
Une autre méthode consiste à transmettre une liste de listes à une routine à un argument:
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]
])
On peut ensuite construire une liste d’arguments avec sa méthode préférée.
À partir de python 3.4.4, vous pouvez utiliser multiprocessing.get_context () pour obtenir un objet de contexte utilisant plusieurs méthodes de démarrage:
import multiprocessing as mp
def foo(q, h, w):
q.put(h + ' ' + w)
print(h + ' ' + w)
if __== '__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()
Ou vous remplacez simplement
pool.map(harvester(text,case),case, 1)
par:
pool.apply_async(harvester(text,case),case, 1)
Il y a beaucoup de réponses ici, mais aucune ne semble fournir un code compatible Python 2/3 qui fonctionnera avec toutes les versions. Si vous voulez que votre code fonctionne avec juste travailler , cela fonctionnera pour l'une ou l'autre version de 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
Après cela, vous pouvez utiliser le multitraitement de la manière habituelle de Python 3, comme vous le souhaitez. Par exemple:
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)
fonctionnera dans Python 2 ou Python 3.
text = "test"
def unpack(args):
return args[0](*args[1:])
def harvester(text, case):
X = case[0]
text+ str(X)
if __== '__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()
Dans la documentation officielle, il est indiqué qu’il ne prend en charge qu’un seul argument itérable. J'aime utiliser apply_async dans de tels cas. Dans ton cas je ferais:
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 __== '__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()
Voici un exemple de la routine que j’utilise pour passer plusieurs arguments à une fonction à un seul argument utilisée dans un pool.imap fork:
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()