Многопроцессорная обработка Python PicklingError: Can't pickle <type 'function'>

Мне жаль, что я не могу воспроизвести ошибку на более простом примере, а мой код слишком сложен для публикации. Если я запускаю программу в оболочке IPython вместо обычного Python, все работает хорошо.

Я посмотрел несколько предыдущих заметок по этой проблеме. Все они были вызваны использованием пула для вызова функции, определенной в функции класса. Но это не так для меня.

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 313, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Буду признателен за любую помощь.

Обновление: функция, которую я выбираю, определяется на верхнем уровне модуля. Хотя он вызывает функцию, которая содержит вложенную функцию. то есть f() вызывает g() вызывает h() с вложенной функцией i(), а я pool.apply_async(f). f(), g(), h() все определены на верхнем уровне. Я попробовал более простой пример с этим шаблоном, и он работает, хотя.

Ответ 1

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

Этот кусок кода:

import multiprocessing as mp

class Foo():
    @staticmethod
    def work(self):
        pass

if __name__ == '__main__':   
    pool = mp.Pool()
    foo = Foo()
    pool.apply_async(foo.work)
    pool.close()
    pool.join()

выдает ошибку, почти идентичную той, которую вы опубликовали:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 315, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Проблема в том, что все методы pool используют mp.SimpleQueue для передачи задач рабочим процессам. Все, что проходит через mp.SimpleQueue, должно быть отборным, а foo.work - отбором, поскольку оно не определено на верхнем уровне модуля.

Это можно исправить, определив функцию на верхнем уровне, которая вызывает foo.work():

def work(foo):
    foo.work()

pool.apply_async(work,args=(foo,))

Обратите внимание, что foo можно выбрать, так как Foo определен на верхнем уровне, а foo.__dict__ можно выбрать.

Ответ 2

Я использовал бы pathos.multiprocesssing вместо multiprocessing. pathos.multiprocessing является fork multiprocessing, который использует dill. dill может сериализовать почти что угодно в python, так что вы можете отправлять гораздо больше вокруг параллельно. Форк pathos также имеет возможность работать непосредственно с несколькими функциями аргументов, как вам нужно для методов класса.

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> p = Pool(4)
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]
>>> 
>>> class Foo(object):
...   @staticmethod
...   def work(self, x):
...     return x+1
... 
>>> f = Foo()
>>> p.apipe(f.work, f, 100)
<processing.pool.ApplyResult object at 0x10504f8d0>
>>> res = _
>>> res.get()
101

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

Ответ 3

Как говорили другие, multiprocessing может передавать объекты Python только рабочим процессам, которые можно мариновать. Если вы не можете реорганизовать свой код, как описано в unutbu, вы можете использовать dill расширенные возможности травления/разбрасывания для передачи данных (особенно данных кода), как показано ниже.

Для этого решения требуется только установка dill и других библиотек как pathos:

import os
from multiprocessing import Pool

import dill


def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    return fun(*args)


def apply_async(pool, fun, args):
    payload = dill.dumps((fun, args))
    return pool.apply_async(run_dill_encoded, (payload,))


if __name__ == "__main__":

    pool = Pool(processes=5)

    # asyn execution of lambda
    jobs = []
    for i in range(10):
        job = apply_async(pool, lambda a, b: (a, b, a * b), (i, i + 1))
        jobs.append(job)

    for job in jobs:
        print job.get()
    print

    # async execution of static method

    class O(object):

        @staticmethod
        def calc():
            return os.getpid()

    jobs = []
    for i in range(10):
        job = apply_async(pool, O.calc, ())
        jobs.append(job)

    for job in jobs:
        print job.get()

Ответ 4

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

Обратите внимание, что это было в Windows (где forking немного менее изящно).

Я запускал:

python -m profile -o output.pstats <script> 

И выяснилось, что удаление профилирования удалило ошибку и поместило ее профилирование. Меня тоже затихало, потому что я знал, что код работал. Я проверял, не обновил ли что-то пул .py... тогда возникло чувство тонуса и устранено профилирование, и это все.

Проводка здесь для архивов в случае, если кто-то еще столкнется с ней.

Ответ 5

Это решение требует только установки укропа и других библиотек в качестве пафоса

def apply_packed_function_for_map((dumped_function, item, args, kwargs),):
    """
    Unpack dumped function as target function and call it with arguments.

    :param (dumped_function, item, args, kwargs):
        a tuple of dumped function and its arguments
    :return:
        result of target function
    """
    target_function = dill.loads(dumped_function)
    res = target_function(item, *args, **kwargs)
    return res


def pack_function_for_map(target_function, items, *args, **kwargs):
    """
    Pack function and arguments to object that can be sent from one
    multiprocessing.Process to another. The main problem is:
        «multiprocessing.Pool.map*» or «apply*»
        cannot use class methods or closures.
    It solves this problem with «dill».
    It works with target function as argument, dumps it («with dill»)
    and returns dumped function with arguments of target function.
    For more performance we dump only target function itself
    and don't dump its arguments.
    How to use (pseudo-code):

        ~>>> import multiprocessing
        ~>>> images = [...]
        ~>>> pool = multiprocessing.Pool(100500)
        ~>>> features = pool.map(
        ~...     *pack_function_for_map(
        ~...         super(Extractor, self).extract_features,
        ~...         images,
        ~...         type='png'
        ~...         **options,
        ~...     )
        ~... )
        ~>>>

    :param target_function:
        function, that you want to execute like  target_function(item, *args, **kwargs).
    :param items:
        list of items for map
    :param args:
        positional arguments for target_function(item, *args, **kwargs)
    :param kwargs:
        named arguments for target_function(item, *args, **kwargs)
    :return: tuple(function_wrapper, dumped_items)
        It returs a tuple with
            * function wrapper, that unpack and call target function;
            * list of packed target function and its' arguments.
    """
    dumped_function = dill.dumps(target_function)
    dumped_items = [(dumped_function, item, args, kwargs) for item in items]
    return apply_packed_function_for_map, dumped_items

Он также работает для массивов numpy.

Ответ 6

Проводите ли вы числовой массив строк случайно?

У меня была такая же точная ошибка, когда я передавал массив, который содержит пустую строку. Я думаю, это может быть из-за этой ошибки: http://projects.scipy.org/numpy/ticket/1658

Ответ 7

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

Эта ошибка также появится, если у вас есть встроенная функция внутри объекта модели, которая была передана заданию async.

Поэтому не забудьте проверить, что объекты модели не имеют встроенных функций. (В нашем случае мы использовали функцию FieldTracker() django-model-utils внутри модели для отслеживания определенного поля). Ниже приведена ссылка на соответствующую проблему GitHub.

Ответ 8

Основываясь на решении @rocksportrocker, было бы целесообразно укропить при отправке и получении результатов.

import dill
import itertools
def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    res = fun(*args)
    res = dill.dumps(res)
    return res

def dill_map_async(pool, fun, args_list,
                   as_tuple=True,
                   **kw):
    if as_tuple:
        args_list = ((x,) for x in args_list)

    it = itertools.izip(
        itertools.cycle([fun]),
        args_list)
    it = itertools.imap(dill.dumps, it)
    return pool.map_async(run_dill_encoded, it, **kw)

if __name__ == '__main__':
    import multiprocessing as mp
    import sys,os
    p = mp.Pool(4)
    res = dill_map_async(p, lambda x:[sys.stdout.write('%s\n'%os.getpid()),x][-1],
                  [lambda x:x+1]*10,)
    res = res.get(timeout=100)
    res = map(dill.loads,res)
    print(res)