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

245

Мне жаль, что я не могу воспроизвести ошибку на более простом примере, а мой код слишком сложен для публикации. Если я запускаю программу в оболочке 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()Все определены на уровне верхней. Я попробовал более простой пример с этим шаблоном, и он работает, хотя.

вендетта
источник
3
Верхний уровень / принятый ответ - это хорошо, но это может означать, что вам нужно реструктурировать код, что может быть болезненным. Я бы порекомендовал всем, у кого есть эта проблема, также прочитать дополнительные ответы, используя dillи pathos. Однако мне не повезло ни с одним из решений при работе с vtkobjects :( Кому-нибудь удалось запустить код Python при параллельной обработке vtkPolyData?
Крис,

Ответы:

306

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

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

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все методы используют a mp.SimpleQueueдля передачи задач рабочим процессам. Все, что проходит через, mp.SimpleQueueдолжно быть отборным и foo.workне может быть отборным, так как оно не определено на верхнем уровне модуля.

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

def work(foo):
    foo.work()

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

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

unutbu
источник
2
Спасибо за ваш ответ. Я обновил свой вопрос. Я не думаю, что причина в этом
Вендетта
7
Чтобы получить PicklingError, в очередь должно быть помещено что-то, что нельзя отобрать. Это может быть функция или ее аргументы. Чтобы узнать больше о проблеме, я предлагаю сделать копию вашей программы и начать анализировать ее, делая ее проще и проще, каждый раз перезапуская программу, чтобы увидеть, остается ли проблема. Когда это станет действительно простым, вы либо обнаружите проблему самостоятельно, либо получите что-то, что вы можете опубликовать здесь.
unutbu
3
Также: если вы определили функцию на верхнем уровне модуля, но она оформлена, тогда ссылка будет на выходные данные декоратора, и вы все равно получите эту ошибку.
bobpoekert
5
Только поздно, на 5 лет, но я просто столкнулся с этим. Оказывается, что «верхний уровень» должен восприниматься более буквально, чем обычно: мне кажется, что определение функции должно предшествовать инициализации пула (т.е. pool = Pool()строки здесь ). Я этого не ожидал, и это может быть причиной того, что проблема ОП осталась.
Андрас Дик
4
В частности, функции можно выбирать только в том случае, если они определены на верхнем уровне модуля. Похоже, что результат применения functool.partialк функции верхнего уровня также может быть засолен, даже если он определен внутри другой функции.
user1071847
96

Я бы использовал pathos.multiprocesssingвместо multiprocessing. pathos.multiprocessingэто вилка, 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

Майк Маккернс
источник
5
работал лакомство Для всех остальных я установил обе библиотеки через: sudo pip install git+https://github.com/uqfoundation/dill.git@masterиsudo pip install git+https://github.com/uqfoundation/pathos.git@master
Alexander McFarlane
5
@AlexanderMcFarlane Я бы не стал устанавливать пакеты с Python sudo(из внешних источников, таких как GitHub). Вместо этого я бы порекомендовал запустить:pip install --user git+...
Крис
Использование просто pip install pathosне работает, к сожалению, и выдает следующее сообщение:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple
11
pip install pathosтеперь работает и pathosсовместим с Python 3.
Майк Маккернс
3
@DanielGoldfarb: multiprocessэто разветвление, multiprocessingгде dillзаменили pickleв нескольких местах в коде ... но, по сути, это все. pathosпредоставляет несколько дополнительных уровней API, multiprocessа также имеет дополнительные бэкэнды. Но это суть этого.
Майк Маккернс
29

Как уже говорили другие, 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()
rocksportrocker
источник
6
Я dillи pathosавтор ... и, хотя вы правы, не так ли лучше, чище и гибче в использовании, pathosкак в моем ответе? Или, может быть, я немного предвзятый…
Майк Маккернс,
4
Я не знал о состоянии pathosна момент написания и хотел представить решение, которое очень близко к ответу. Теперь, когда я увидел ваше решение, я согласен, что это путь.
Rockportrocker
Я прочитал ваше решение и был как, Doh… I didn't even think of doing it like that. так что это было круто.
Майк Маккернс
4
Спасибо за публикацию, я использовал этот подход для аргументов, которые не могут быть
засечены
@rocksportrocker. Я читаю этот пример и не могу понять, почему существует явный forцикл. Обычно я вижу параллельную подпрограмму, берущую список и возвращающую список без цикла.
user1700890
20

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

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

Я бегал:

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

И обнаружил, что удаление профилирования удалило ошибку, а размещение профилирования восстановило ее. Сводил меня с ума, потому что я знал, что код работал. Я проверял, обновлял ли что-то файл pool.py ... затем чувствовал себя погруженным и устранял профилирование, вот и все.

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

Иезекииль Круглик
источник
3
ВАУ, спасибо за упоминание! Это сводило меня с ума в течение последнего часа или около того; Я попробовал все до очень простого примера - ничего не получалось. Но у меня также был профилировщик, запускающий мой пакетный файл :(
Тим
1
О, не могу отблагодарить тебя достаточно. Это звучит так глупо, хотя и так неожиданно. Я думаю, что это должно быть упомянуто в документах. Все, что у меня было, - это оператор import pdb, и простая функция верхнего уровня с параметром просто passне была «замаскирована».
0xc0de
10

Когда эта проблема возникает с multiprocessingпростым решением является переход от Poolк ThreadPool. Это может быть сделано без изменения кода, кроме

from multiprocessing.pool import ThreadPool as Pool

Это работает, потому что ThreadPool делит память с основным потоком, а не создает новый процесс - это означает, что травление не требуется.

Недостатком этого метода является то, что python не самый лучший язык для обработки потоков - он использует что-то, называемое Global Interpreter Lock, для обеспечения безопасности потока, что может замедлить некоторые варианты использования. Однако, если вы в первую очередь взаимодействуете с другими системами (выполняете команды HTTP, общаетесь с базой данных, записываете в файловые системы), тогда ваш код, скорее всего, не связан с процессором и не будет сильно ударяться. На самом деле, при написании тестов HTTP / HTTPS я обнаружил, что используемая здесь многопоточная модель имеет меньше накладных расходов и задержек, так как накладные расходы на создание новых процессов значительно превышают накладные расходы на создание новых потоков.

Так что, если вы обрабатываете массу вещей в пользовательском пространстве Python, это может быть не лучшим методом.

tedivm
источник
2
Но тогда вы используете только один процессор (по крайней мере, в обычных версиях Python, использующих GIL ), что побеждает цель.
Endre Both
Это действительно зависит от цели. Глобальная блокировка интерпретатора означает, что только один экземпляр за раз может выполнять код Python, но для действий, которые сильно блокируют (доступ к файловой системе, загрузка больших или нескольких файлов, запуск внешнего кода), GIL перестает быть проблемой. В некоторых случаях издержки от открытия новых процессов (а не потоков) перевешивают издержки GIL.
Tedivm
Это правда, спасибо. Тем не менее, вы можете включить оговорку в ответ. В наши дни, когда увеличение вычислительной мощности происходит главным образом в виде более мощных, а не более мощных процессорных ядер, переключение с многоядерного на одноядерное выполнение является довольно существенным побочным эффектом.
Endre Both
Хороший вопрос - я обновил ответ с более подробной информацией. Однако я хочу отметить, что переключение на многопоточную многопоточность не делает Python функционирующим только на одном ядре.
tedivm
4

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

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.

Илья w495 Никитин
источник
3
Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

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

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

Пенки Суреш
источник
0

Основываясь на решении @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)
должны увидеть
источник