Многопроцессорная обработка функции с несколькими входами

#python #multiprocessing #python-multiprocessing

#python #многопроцессорная обработка #python-многопроцессорная обработка

Вопрос:

В Python multiprocessing модуль может использоваться для параллельного запуска функции над диапазоном значений. Например, это создает список из первых 100000 оценок f.

 def f(i):
    return i * i

def main():
    import multiprocessing
    pool = multiprocessing.Pool(2)
    ans = pool.map(f, range(100000))

    return ans
  

Можно ли сделать аналогичную вещь, когда f принимает несколько входных данных, но изменяется только одна переменная? Например, как бы вы распараллелили это:

 def f(i, n):
    return i * i   2*n

def main():
    ans = []
    for i in range(100000):
        ans.append(f(i, 20))

    return ans
  

Ответ №1:

Вы можете использовать functools.partial()

 def f(i, n):
    return i * i   2*n

def main():
    import multiprocessing
    pool = multiprocessing.Pool(2)
    ans = pool.map(functools.partial(f, n=20), range(100000))

    return ans
  

Комментарии:

1. Я знаю, что это разрешено, но почему, учитывая, что могут быть изменены только функции, определенные на верхнем уровне модуля?

2. Можете ли вы прояснить момент об использовании partial — похоже, что он игнорирует ключи для аргумента: если я хочу использовать pool.map для ВТОРОГО аргумента — partial(f, i=20) — Я получил ошибку: получил несколько значений для аргумента i .

3. @Mikhail_Sam docs.python.org/2/library/functools.html#functools.partial Функция, которую вы добавляете к частичному, должна иметь первый аргумент в качестве позиционного аргумента (например, ‘i’ при запуске цикла for), а остальные аргументы ключевого слова должны быть после этого. Все значения ‘i’ добавляются в виде списка / диапазона в качестве второго аргумента функции ‘pool.map’. В вашем примере вы указали значение ‘i’ в частичной функции, когда значения для ‘i’ уже доступны в качестве второго аргумента функции ‘pool’, что приводит к самоочевидной ошибке/

Ответ №2:

Есть несколько способов сделать это. В примере, приведенном в вопросе, вы могли бы просто определить функцию-оболочку

 def g(i):
    return f(i, 20)
  

и передайте эту оболочку map() . Более общий подход заключается в том, чтобы иметь оболочку, которая принимает один аргумент кортежа и распаковывает кортеж в несколько аргументов

 def g(tup):
    return f(*tup)
  

или используйте эквивалентное лямбда-выражение: lambda tup: f(*tup) .

Ответ №3:

Если вы используете мой fork of multiprocessing , called pathos , вы можете получить пулы, которые принимают несколько аргументов… а также принимать lambda функции. Самое приятное в этом то, что вам не нужно изменять свои программные конструкции, чтобы они соответствовали параллельной работе.

 >>> def f(i, n):
...   return i * i   2*n
... 
>>> from itertools import repeat
>>> N = 10000
>>>
>>> from pathos.pools import ProcessPool as Pool
>>> pool = Pool()
>>>
>>> ans = pool.map(f, xrange(1000), repeat(20))
>>> ans[:10]
[40, 41, 44, 49, 56, 65, 76, 89, 104, 121]
>>>
>>> # this also works
>>> ans = pool.map(lambda x: f(x, 20), xrange(1000))
>>> ans[:10]
[40, 41, 44, 49, 56, 65, 76, 89, 104, 121]
  

Комментарии:

1. Только что установленный pathos — гораздо приятнее использовать локальные функции с замыканиями и т.д. без каких-либо глобальных частичных функций или функций-оболочек или чего-либо еще. Спасибо за это.

2. @AlexL: обратите внимание, что если вам нужен точно такой же интерфейс, что и multiprocessing , но с лучшей сериализацией, вы можете использовать альтернативно multiprocess ( pathos устанавливает его как зависимость).

Ответ №4:

Этот метод известен как каррирование:https://en.wikipedia.org/wiki/Currying

Другой способ сделать это без использования, functools.partial используя классическую map команду внутри pool.map :

 def f(args):
   x, fixed = args
   # FUNCTIONALITY HERE

pool = multiprocessing.Pool(multiprocessing.cpu_count() - 1)
pool.map(f, map(lambda x: (x, fixed), arguments))
  

Ответ №5:

Вы можете использовать каррирование бедняка (иначе обернуть его):

 new_f = lambda x: f(x, 20)
  

затем вызовите new_f(i) .

Комментарии:

1. Thils не будет работать с картой многопроцессорной обработки, потому что она не поддерживает функции, которые не являются «импортируемыми» (с использованием инструмента pickle)