2015-03-03 2 views
3

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

В принципе, у меня есть иерархическая организация функций, и это мешает мне многопроцессорствовать на верхнем уровне. К сожалению, я не верю, что могу изменить макет программы, потому что мне нужны все переменные, которые я создаю после начальных входов.

Например, у меня есть это:

import multiprocessing 

    def calculate(x): 
    # here is where I would take this input x (and maybe a couple more inputs) 
    # and build a larger library of variables that I use further down the line 

    def domath(y): 
     return x * y 

    pool = multiprocessing.Pool(3) 
    final= pool.map(domath, range(3)) 

calculate(2) 

Это дает следующее сообщение об ошибке:

Can't pickle <type 'function'>: attribute lookup __builtin__.function failed 

Я думал глобалов, но я боюсь, что я должен был бы определить слишком много, и это может замедлить мою программу совсем немного. Есть ли какое-либо обходное решение без перестройки всей программы?

+0

Вы можете прочитать ответ, который я выбрал, пытаясь решить [в значительной степени та же ошибка] (http://stackoverflow.com/questions/6234586/we-need-to-pickle-any-sort-of-callable). – motoku

+0

Эй, Шон, посмотрев на ваш вопрос, я боюсь, что смогу выйти из глубины решения. В любом случае, вы могли бы дать мне более концептуальное изложение того, что делают эти функции при упаковке и распаковке? – Tim

+0

Конечно. Позвольте мне собрать пример кода. – motoku

ответ

2

Вы можете использовать pathos.multiprocessing, который является ответвлением multiprocessing, который использует dill сериалайзер вместо pickle. dill может сериализовать почти что угодно в python. Тогда вам не нужно редактировать код.

>>> from pathos.multiprocessing import ProcessingPool as Pool 
>>> 
>>> def calculate(x): 
... def domath(y): 
...  return x*y 
... return Pool().map(domath, range(3)) 
... 
>>> calculate(2) 
[0, 2, 4] 

Вы можете даже сходить с ума ... так как большинство вещей маринованное. Нет необходимости в нечетных непифонных решениях, которые нужно приготовить с чистым multiprocessing.

>>> class Foo(object): 
... def __init__(self, x): 
...  self.x = x 
... def doit(self, y): 
...  return ProcessingPool().map(self.squared, calculate(y+self.x)) 
... def squared(self, z): 
...  return z*z 
... 
>>> def thing(obj, y): 
... return getattr(obj, 'doit')(y) 
... 
>>> ProcessingPool().map(thing, ProcessingPool().map(Foo, range(3)), range(3)) 
[[0, 0, 0], [0, 4, 16], [0, 16, 64]] 

Получить pathos здесь: https://github.com/uqfoundation

+0

Спасибо, Майк! Это получилось здорово. Быстрая заметка о файле, доступном на http://trac.mystic.cacr.caltech.edu/project/pathos/wiki/Installation Эта версия (по крайней мере, когда я ее загрузил) не содержит включенного многопроцессорного модуля. Я думаю, что это может быть причиной того, что у нескольких пользователей возникли проблемы с импортом многопроцессорной обработки через пафос. После того, как я загрузил файлы из github вместо страницы пафоса, у меня было все, что мне нужно. – Tim

+0

Прохладный. Да, я знаю .. спасибо за напоминание. Версия на ссылку 'pathos' wiki, которую вы упоминаете, очень старая (timestamp: 06/28/10 17:50). Код github обновлен, и новый выпуск «неизбежен». В то время я обновляю все ссылки с новой стабильной версией. –

2

Проблема, с которой вы столкнулись, на самом деле является особенностью. Источник рассола фактически предназначен для предотвращения такого поведения, чтобы предотвратить выполнение вредоносного кода. Пожалуйста, учтите, что при решении любой применимой реализации безопасности.

Прежде всего, у нас есть импорт.

import marshal 
import pickle 
import types 

Здесь мы имеем функцию, которая принимает в функции в качестве аргумента, соленья части объекта, а затем возвращает кортеж, содержащий все детали:

def pack(fn): 
    code = marshal.dumps(fn.__code__) 
    name = pickle.dumps(fn.__name__) 
    defs = pickle.dumps(fn.__defaults__) 
    clos = pickle.dumps(fn.__closure__) 
    return (code, name, defs, clos) 

Далее мы имеем функцию, которая берет четыре части нашей преобразованной функции. Он переводит эти четыре части и создает затем возвращает функцию из этих частей. Вы должны принять к сведению, что Глобал повторно вводят здесь потому, что наш процесс не обрабатывает эти:

def unpack(code, name, defs, clos): 
    code = marshal.loads(code) 
    glob = globals() 
    name = pickle.loads(name) 
    defs = pickle.loads(defs) 
    clos = pickle.loads(clos) 
    return types.FunctionType(code, glob, name, defs, clos) 

Здесь мы имеем тестовую функцию. Заметьте, что я ввожу импорт в область действия функции. Глобал не обрабатываются через наш процесс травления:

def test_function(a, b): 
    from random import randint 
    return randint(a, b) 

Наконец мы упаковываем наш тестовый объект и распечатать результат, чтобы убедиться, что все работает:

packed = pack(test_function) 
print((packed)) 

Наконец, мы распаковываем нашу функцию, присвоить его переменную, вызвать ее и распечатать ее вывод:

unpacked = unpack(*packed) 
print((unpacked(2, 20))) 

Комментарий, если у вас есть вопросы.

+0

Спасибо, что нашли время, чтобы объяснить гораздо более подробно! Я пытаюсь применить это к приведенному выше примеру, но у меня все еще возникают проблемы. Когда он распаковывается, не возвращает ли он ту же форматированную функцию, что и раньше? Я все еще получаю ошибку травления, когда пробегаю по этому методу (что, вероятно, означает, что я использую его неправильно). EDIT: что вы думаете о @mike? – Tim

+0

@Tim Да, функция «распакованная» должна быть такой же, как у оригинала. Если вы хотите сохранить исходный источник, вам нужно будет обрабатывать строки. Что касается ответа Майка, я думаю, что это, вероятно, лучший вариант. – motoku

+0

@SeanPedersen: это, по сути, то, что 'dill' делает для вас, за исключением того, что он создает тот же вызываемый тип, который был сохранен ... так что если вы замачиваете лямбда, вы получаете лямбду назад или если вы распиливаете связанный метод, вы получаете связанный метод назад и т. д. Он также обрабатывает глобальные переменные для вас в большинстве случаев, а также позволяет обрабатывать '__main__' как модуль. –

0

Как насчет принятия встроенной функции вне дома?

Это кажется мне ясное решение (так как вы не дали свой ожидаемый результат, я должен был догадаться):

$ cat /tmp/tmp.py 
import multiprocessing 

def calculate(x): 
    # here is where I would take this input x (and maybe a couple more inputs) 
    # and build a larger library of variables that I use further down the line 

    pool = multiprocessing.Pool(3) 
    _lst = [(x, y) for x in (x,) for y in range(3)] 
    final= pool.map(domath, _lst) 
    print(final) 

def domath(l): 
    return l[0] * l[1] 

calculate(2) 

$ python /tmp/tmp.py 
[0, 2, 4] 

$