Pool.map multiprocessing di Python per più argomenti


536

Nella libreria multiprocessing di Python, esiste una variante di pool.map che supporta più argomenti?

text = "test"
def harvester(text, case):
    X = case[0]
    text+ str(X)

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=6)
    case = RAW_DATASET
    pool.map(harvester(text,case),case, 1)
    pool.close()
    pool.join()

4
Con mia sorpresa, non ho potuto partiallambdafare né fare questo. Penso che abbia a che fare con lo strano modo in cui le funzioni vengono passate ai sottoprocessi (via pickle).
mittente

10
@senderle: questo è un bug in Python 2.6, ma è stato corretto a partire dalla 2.7: bugs.python.org/issue5228
unutbu il

1
Basta semplicemente sostituire pool.map(harvester(text,case),case, 1) con: pool.apply_async(harvester(text,case),case, 1)
Tung Nguyen il

3
@Syrtis_Major, per favore non modificare le domande OP che distorcono effettivamente le risposte che sono state precedentemente fornite. L'aggiunta returndi harvester()risposta s' @senderie trasformato in essere imprecisi. Ciò non aiuta i futuri lettori.
Ricalsin,

1
Direi che una soluzione semplice sarebbe quella di impacchettare tutti gli arg in una tupla e decomprimerlo nella funzione di esecuzione. L'ho fatto quando avevo bisogno di inviare argomenti multipli complessi a una funzione eseguita da un pool di processi.
HS Rathore,

Risposte:


358

La risposta a questo dipende dalla versione e dalla situazione. La risposta più generale per le recenti versioni di Python (dalla 3.3) è stata descritta per la prima volta da JF Sebastian . 1 Utilizza il Pool.starmapmetodo, che accetta una sequenza di tuple di argomenti. Quindi decomprime automaticamente gli argomenti da ciascuna tupla e li passa alla funzione data:

import multiprocessing
from itertools import product

def merge_names(a, b):
    return '{} & {}'.format(a, b)

if __name__ == '__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', ...

Per le versioni precedenti di Python, dovrai scrivere una funzione di supporto per decomprimere gli argomenti in modo esplicito. Se vuoi usare with, dovrai anche scrivere un wrapper per trasformarlo Poolin un gestore di contesto. (Grazie a muon per averlo sottolineato.)

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 __name__ == '__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', ...

In casi più semplici, con un secondo argomento fisso, puoi anche usare partial, ma solo in 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 __name__ == '__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. Gran parte di questo è stato ispirato dalla sua risposta, che probabilmente avrebbe dovuto essere invece accettata. Ma poiché questo è bloccato nella parte superiore, mi è sembrato meglio migliorarlo per i futuri lettori.


Mi sembra che RAW_DATASET in questo caso dovrebbe essere una variabile globale? Mentre voglio il partial_harvester cambiare il valore di case in ogni call di harvester (). Come raggiungerlo?
xgdgsc,

La cosa più importante qui è assegnare =RAW_DATASETil valore predefinito a case. Altrimenti pool.mapconfonderai sugli argomenti multipli.
Emerson Xu,

1
Sono confuso, cosa è successo alla textvariabile nel tuo esempio? Perché RAW_DATASETsembra essere passato due volte. Penso che potresti avere un refuso?
Dave,

non so perché l'utilizzo with .. as .. mi dia AttributeError: __exit__, ma funziona benissimo se chiamo e pool = Pool();poi chiudo manualmente pool.close()(python2.7)
muon

1
@muon, buona cattura. Sembra che gli Poologgetti non diventino gestori di contesto fino a Python 3.3. Ho aggiunto una semplice funzione wrapper che restituisce un Poolgestore di contesto.
Senderle,

501

esiste una variante di pool.map che supporta più argomenti?

Python 3.3 include il pool.starmap()metodo :

#!/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()

Per le versioni precedenti:

#!/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()

Produzione

1 1
2 1
3 1

Nota come itertools.izip()e come itertools.repeat()vengono usati qui.

A causa del bug citato da @unutbu non è possibile utilizzare functools.partial()o funzionalità simili su Python 2.6, quindi la funzione wrapper semplice func_star()deve essere definita in modo esplicito. Vedi anche la soluzione suggerita dauptimebox .


1
F .: È possibile decomprimere la tupla argomento nella firma di func_starquesto modo: def func_star((a, b)). Naturalmente, questo funziona solo per un numero fisso di argomenti, ma se questo è l'unico caso che ha, è più leggibile.
Björn Pollex,

1
@ Space_C0wb0y: la f((a,b))sintassi è obsoleta e rimossa in py3k. Ed è inutile qui.
jfs,

forse più pitonico: func = lambda x: func(*x)invece di definire una funzione wrapper
dylam,

1
@ zthomas.nc questa domanda riguarda come supportare più argomenti per multiprocessing pool.map. Se vuoi sapere come chiamare un metodo anziché una funzione in un diverso processo Python tramite multiprocessing, fai una domanda separata (se tutto il resto fallisce, puoi sempre creare una funzione globale che avvolge la chiamata del metodo simile a quella func_star()precedente)
jfs

1
Vorrei che ci fossero starstarmap.
Константин Ван

141

Penso che il seguito sarà migliore

def multi_run_wrapper(args):
   return add(*args)
def add(x,y):
    return x+y
if __name__ == "__main__":
    from multiprocessing import Pool
    pool = Pool(4)
    results = pool.map(multi_run_wrapper,[(1,2),(2,3),(3,4)])
    print results

produzione

[3, 5, 7]

16
La soluzione più semplice. C'è una piccola ottimizzazione; rimuovere la funzione wrapper e decomprimere argsdirettamente in add, funziona per qualsiasi numero di argomenti:def add(args): (x,y) = args
Ahmed

1
potresti anche usare una lambdafunzione invece di definiremulti_run_wrapper(..)
Andre Holzner

2
hm ... in effetti, usare a lambdanon funziona perché pool.map(..)prova a decapitare la funzione data
Andre Holzner

Come si usa questo se si desidera archiviare il risultato addin un elenco?
Vivek Subramanian,

@Ahmed Mi piace così com'è, perché IMHO la chiamata del metodo dovrebbe fallire, ogni volta che il numero di parametro non è corretto.
Michael Dorner,

56

Utilizzo di Python 3.3+ conpool.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()

Risultato:

1 --- 4
2 --- 5
3 --- 6

Puoi anche zip () più argomenti se ti piace: zip(a,b,c,d,e)

Nel caso in cui si desidera avere un valore costante passato come argomento è necessario utilizzare import itertoolse quindi zip(itertools.repeat(constant), a), per esempio.


2
Questa è una risposta duplicata quasi esatta come quella di @JFSebastian nel 2011 (con oltre 60 voti).
Mike McKerns,

29
No. Prima di tutto ha rimosso molte cose inutili e afferma chiaramente che è per Python 3.3+ ed è destinato ai principianti che cercano una risposta semplice e chiara. Come un principiante me stesso ci è voluto del tempo per capirlo in quel modo (sì con i post di JFSebastians) ed è per questo che ho scritto il mio post per aiutare altri principianti, perché il suo post diceva semplicemente "c'è lo starmap" ma non lo ha spiegato - questo è ciò che intende il mio post. Quindi non c'è assolutamente alcun motivo per colpirmi con due voti negativi.
user136036

Nel 2011, non c'era "+" in Python 3.3 + ... quindi ovviamente.
Mike McKerns,

27

Avendo appreso degli itertools nella risposta di JF Sebastian, ho deciso di fare un ulteriore passo avanti e scrivere un parmappacchetto che si occupa della parallelizzazione, dell'offerta mape delle starmapfunzioni su python-2.7 e python-3.2 (e versioni successive) che possono accettare qualsiasi numero di argomenti posizionali .

Installazione

pip install parmap

Come parallelizzare:

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)

Ho caricato parmap su PyPI e su un repository github .

Ad esempio, è possibile rispondere alla domanda come segue:

import parmap

def harvester(case, text):
    X = case[0]
    text+ str(X)

if __name__ == "__main__":
    case = RAW_DATASET  # assuming this is an iterable
    parmap.map(harvester, case, "test", chunksize=1)

20

# "Come accettare più argomenti".

def f1(args):
    a, b, c = args[0] , args[1] , args[2]
    return a+b+c

if __name__ == "__main__":
    import multiprocessing
    pool = multiprocessing.Pool(4) 

    result1 = pool.map(f1, [ [1,2,3] ])
    print(result1)

2
Pulito ed elegante.
Prav001,

1
Non capisco perché devo scorrere fino in fondo qui per trovare la risposta migliore.
Toti

12

C'è un fork di pathosmultiprocessing chiamato ( nota: usa la versione su github ) che non ha bisogno : le funzioni della mappa rispecchiano l'API per la mappa di Python, quindi la mappa può accettare più argomenti. Con , in genere, puoi anche eseguire il multiprocessing nell'interprete, invece di rimanere bloccato nel blocco. Pathos è in procinto di essere rilasciato, dopo alcuni lievi aggiornamenti, principalmente la conversione in Python 3.x.starmappathos__main__

  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]

pathosha diversi modi in cui puoi ottenere il comportamento esatto di starmap.

>>> def add(*x):
...   return sum(x)
... 
>>> x = [[1,2,3],[4,5,6]]
>>> import pathos
>>> import numpy as np
>>> # use ProcessPool's map and transposing the inputs
>>> pp = pathos.pools.ProcessPool()
>>> pp.map(add, *np.array(x).T)
[6, 15]
>>> # use ProcessPool's map and a lambda to apply the star
>>> pp.map(lambda x: add(*x), x)
[6, 15]
>>> # use a _ProcessPool, which has starmap
>>> _pp = pathos.pools._ProcessPool()
>>> _pp.starmap(add, x)
[6, 15]
>>> 

Voglio notare che questo non riguarda la struttura nella domanda originale. [[1,2,3], [4,5,6]] disimballerebbe con lo starmap su [pow (1,2,3), pow (4,5,6)], non [pow (1,4) , pow (2,5), pow (3, 6)]. Se non si ha un buon controllo sugli ingressi che vengono passati alla propria funzione, potrebbe essere necessario prima ristrutturarli.
Scott,

@Scott: ah, non me ne sono accorto ... oltre 5 anni fa. Farò un piccolo aggiornamento. Grazie.
Mike McKerns,

8

È possibile utilizzare le seguenti due funzioni in modo da evitare di scrivere un wrapper per ogni nuova funzione:

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

Utilizzare la funzione functioncon le liste degli argomenti arg_0, arg_1e arg_2come segue:

pool = Pool(n_core)
list_model = pool.map(universal_worker, pool_args(function, arg_0, arg_1, arg_2)
pool.close()
pool.join()

8

Una soluzione migliore per 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

su[]:

[3, 5, 7]


7

Un'altra semplice alternativa è avvolgere i parametri della funzione in una tupla e quindi avvolgere i parametri che dovrebbero essere passati anche in tuple. Questo forse non è l'ideale quando si tratta di grandi quantità di dati. Credo che farebbe copie per ogni tupla.

from multiprocessing import Pool

def f((a,b,c,d)):
    print a,b,c,d
    return a + b + c +d

if __name__ == '__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()

Fornisce l'output in un ordine casuale:

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]

Anzi, continua a cercare un modo migliore :(
Fábio Dias,

6

Un modo migliore è usare decoratore invece di scrivere manualmente la funzione wrapper . Soprattutto quando hai molte funzioni da mappare, Decorator ti farà risparmiare tempo evitando di scrivere wrapper per ogni funzione. Di solito una funzione decorata non è selezionabile, tuttavia possiamo usarla functoolsper aggirarla . Altre disscusioni possono essere trovate qui .

Ecco l'esempio

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

Quindi puoi mapparlo con argomenti compressi

np, xlist, ylist = 2, range(10), range(10)
pool = Pool(np)
res = pool.map(func, zip(xlist, ylist))
pool.close()
pool.join()

Ovviamente, puoi sempre usare Pool.starmapin Python 3 (> = 3.3) come indicato in altre risposte.


I risultati non sono quelli previsti: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] Mi aspetterei: [0,1,2,3,4,5,6,7,8, 9,1,2,3,4,5,6,7,8,9,10,2,3,4,5,6,7,8,9,10,11, ...
Tedo Vrbanec,

I risultati di @TedoVrbanec dovrebbero essere [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]. Se vuoi quello successivo, puoi usare itertools.productinvece di zip.
Syrtis Major,

4

Un altro modo è passare un elenco di elenchi a una routine a argomento singolo:

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

Si può costruire un elenco di argomenti con il proprio metodo preferito.


Questo è un modo semplice, ma è necessario modificare le funzioni originali. Inoltre, alcune volte ricordano le funzioni di altri che potrebbero non essere modificabili.
WeizhongTu,

Dirò che questo si attacca allo zen Python. Dovrebbe esserci uno e un solo modo ovvio per farlo. Se per caso sei l'autore della funzione chiamante, questo dovrebbe usare questo metodo, per altri casi possiamo usare il metodo imotai.
Nehem,

La mia scelta è quella di usare una tupla, quindi scartarli immediatamente come prima cosa nella prima riga.
Nehem,

3

Ecco un altro modo per farlo che IMHO è più semplice ed elegante di qualsiasi altra risposta fornita.

Questo programma ha una funzione che accetta due parametri, li stampa e stampa anche la somma:

import multiprocessing

def main():

    with multiprocessing.Pool(10) as pool:
        params = [ (2, 2), (3, 3), (4, 4) ]
        pool.starmap(printSum, params)
    # end with

# end function

def printSum(num1, num2):
    mySum = num1 + num2
    print('num1 = ' + str(num1) + ', num2 = ' + str(num2) + ', sum = ' + str(mySum))
# end function

if __name__ == '__main__':
    main()

l'output è:

num1 = 2, num2 = 2, sum = 4
num1 = 3, num2 = 3, sum = 6
num1 = 4, num2 = 4, sum = 8

Vedi i documenti di Python per maggiori informazioni:

https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool

In particolare, assicurati di controllare la starmapfunzione.

Sto usando Python 3.6, non sono sicuro che funzionerà con le versioni precedenti di Python

Perché non c'è un esempio molto semplice come questo nei documenti, non ne sono sicuro.


2

Da python 3.4.4, è possibile utilizzare multiprocessing.get_context () per ottenere un oggetto di contesto per utilizzare più metodi di avvio:

import multiprocessing as mp

def foo(q, h, w):
    q.put(h + ' ' + w)
    print(h + ' ' + w)

if __name__ == '__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()

O semplicemente sostituisci

pool.map(harvester(text,case),case, 1)

di:

pool.apply_async(harvester(text,case),case, 1)

2

Ci sono molte risposte qui, ma nessuna sembra fornire codice compatibile con Python 2/3 che funzionerà su qualsiasi versione. Se vuoi che il tuo codice funzioni , funzionerà con entrambe le versioni di 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

Dopodiché, puoi usare il multiprocessing nel normale modo di Python 3, come preferisci. Per esempio:

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)

funzionerà in Python 2 o Python 3.


1

Nella documentazione ufficiale afferma che supporta solo un argomento iterabile. Mi piace usare apply_async in questi casi. Nel tuo caso farei:

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 __name__ == '__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()

1
text = "test"

def unpack(args):
    return args[0](*args[1:])

def harvester(text, case):
    X = case[0]
    text+ str(X)

if __name__ == '__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()

1

Questo è un esempio della routine che uso per passare più argomenti a una funzione a argomento singolo utilizzata in un fork pool.imap :

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

-3

per python2, puoi usare questo trucco

def fun(a,b):
    return a+b

pool = multiprocessing.Pool(processes=6)
b=233
pool.map(lambda x:fun(x,b),range(1000))

perché b = 233. sconfigge lo scopo della domanda
come - se il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.