Подтвердить что ты не робот

Декоратор Python с многопроцессорной обработкой не работает

Я хотел бы использовать декоратор для функции, которую потом перейду в многопроцессорный пул. Тем не менее, код выходит из строя с помощью "PicklingError: Can not pickle: find lookup __builtin__.function failed". Я не совсем понимаю, почему здесь это не удается. Я уверен, что это что-то простое, но я не могу его найти. Ниже приведен минимальный "рабочий" пример. Я думал, что использование функции functools было бы достаточно, чтобы это сработало.

Если я прокомментирую функцию украшения, она работает без проблем. Что это за multiprocessing, что я не понимаю здесь? Есть ли способ сделать эту работу?

Изменить: добавив как декоратор класса, вызывающий класс, так и декоратор функции, выясняется, что декоратор функции работает должным образом. Вызываемый класс-декоратор продолжает терпеть неудачу. Что это за вызываемая версия класса, которая препятствует ее маринованию?

import random
import multiprocessing
import functools

class my_decorator_class(object):
    def __init__(self, target):
        self.target = target
        try:
            functools.update_wrapper(self, target)
        except:
            pass

    def __call__(self, elements):
        f = []
        for element in elements:
            f.append(self.target([element])[0])
        return f

def my_decorator_function(target):
    @functools.wraps(target)
    def inner(elements):
        f = []
        for element in elements:
            f.append(target([element])[0])
        return f
    return inner

@my_decorator_function
def my_func(elements):
    f = []
    for element in elements:
        f.append(sum(element))
    return f

if __name__ == '__main__':
    elements = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)]
    pool = multiprocessing.Pool(processes=4)
    results = [pool.apply_async(my_func, ([e],)) for e in elements]
    pool.close()
    f = [r.get()[0] for r in results]
    print(f)
4b9b3361

Ответ 1

Проблема в том, что маринование должно иметь какой-то способ собрать все, что вы соберете. См. Здесь список того, что можно мариновать:

http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled

При травлении my_func необходимо смазать следующие компоненты:

  • Экземпляр класса my_decorator_class, называемый my_func

    Это нормально. Pickle сохранит имя класса и рассортирует его содержимое __dict__. При рассыпании он использует имя для поиска класса, затем создает экземпляр и заполняет содержимое __dict__. Однако содержимое __dict__ представляет проблему...

  • Экземпляр исходного my_func, который хранится в my_func.target

    Это не так хорошо. Это функция на верхнем уровне, и обычно их можно мариновать. Pickle сохранит имя функции. Проблема, однако, в том, что имя "my_func" больше не привязано к неразделенной функции, оно связано с украшенной функцией. Это означает, что рассол не сможет найти неубранную функцию для воссоздания объекта. К сожалению, маринование не имеет никакого способа узнать, что объект, который он пытается рассолить, всегда можно найти под названием main.my_func.

Вы можете изменить его так и он будет работать:

import random
import multiprocessing
import functools

class my_decorator(object):
    def __init__(self, target):
        self.target = target
        try:
            functools.update_wrapper(self, target)
        except:
            pass

    def __call__(self, candidates, args):
        f = []
        for candidate in candidates:
            f.append(self.target([candidate], args)[0])
        return f

def old_my_func(candidates, args):
    f = []
    for c in candidates:
        f.append(sum(c))
    return f

my_func = my_decorator(old_my_func)

if __name__ == '__main__':
    candidates = [[random.randint(0, 9) for _ in range(5)] for _ in range(10)]
    pool = multiprocessing.Pool(processes=4)
    results = [pool.apply_async(my_func, ([c], {})) for c in candidates]
    pool.close()
    f = [r.get()[0] for r in results]
    print(f)

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