web-dev-qa-db-fra.com

Python multiprocessing pool.map pour plusieurs arguments

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()
347
user642897

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.

221
senderle

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()

Sortie

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 .

374
jfs

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]
117
imotai

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.

42
user136036

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)
22
zeehio

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]
9
Mike McKerns

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()
7
Alfred M.

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]

6
xmduhan

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]
6
Alex Klibisz

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.

6
Syrtis Major

# "Comment prendre plusieurs arguments".

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)
4
Dane Lee

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.

3
Adobe

À 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)
2
Tung Nguyen

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.

0
cgnorthcutt
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()
0
Jaime RS

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()
0
A. Nodar