Багатопроцесорний Python PicklingError: Неможливо підсолити <тип 'функції'>


243

Мені шкода, що я не можу відтворити помилку більш простим прикладом, і мій код занадто складний для публікації. Якщо я запускаю програму в оболонці 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?
Кріс,

Відповіді:


305

Ось список того, що можна замаринувати . Зокрема, функції вибираються лише у тому випадку, якщо вони визначені на верхньому рівні модуля.

Цей фрагмент коду:

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__є підбираючим.


2
Спасибі за Вашу відповідь. Я оновив своє запитання. Я не думаю, що це є причиною
Вендетта,

7
Щоб отримати PicklingError, у черзі потрібно поставити щось, що не підлягає вибору. Це може бути функція або її аргументи. Щоб дізнатися більше про проблему, я пропоную зробити копію вашої програми та почати її розбирати, роблячи її все простішою та простішою, щоразу перезапускаючи програму, щоб побачити, чи проблема залишається. Коли це стане дійсно простим, ви або самі виявили проблему, або у вас з’явиться щось, що ви можете опублікувати тут.
unutbu

3
Також: якщо ви визначите функцію на верхньому рівні модуля, але вона декорована, тоді посилання буде на вихід декоратора, і ви все одно отримаєте цю помилку.
bobpoekert

5
Лише пізно на 5 років, але я просто натрапив на це. Виявляється, що "верхній рівень" слід сприймати буквальніше, ніж зазвичай: мені здається, що визначення функції повинно передувати ініціалізації пулу (тобто pool = Pool()рядок тут ). Я цього не очікував, і це може бути причиною, чому проблема ОП зберігається.
Андрас Дік

4
Зокрема, функції вибираються лише у тому випадку, якщо вони визначені на верхньому рівні модуля. Виявляється, що результат застосування functool.partialдо функції вищого рівня також може підбиратися, навіть якщо він визначений всередині іншої функції.
користувач1071847

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
Олександр Макфарлейн

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 робочим процесам, які можна замаринувати. Якщо ви не можете реорганізувати свій код, як описано унутбу, ви можете використовувати 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()

6
Я dillі pathosавтор… і, хоча ви маєте рацію, чи не так симпатичніше, чистіше і гнучкіше використовувати, pathosяк у моїй відповіді? А може, я трохи упереджений…
Майк Маккернс

4
Я не знав про стан pathosна момент написання і хотів представити рішення, яке дуже близьке до відповіді. Тепер, коли я бачив ваше рішення, я погоджуюся, що це шлях.
rockportrocker

Я прочитав ваше рішення і був начебто, Doh… I didn't even think of doing it like that. це було якось круто.
Майк Маккернс

4
Дякую за розміщення, я використовував цей підхід для Dilling / undilling аргументів , які не можуть бути маринованим: stackoverflow.com/questions/27883574 / ...
jazzblue

@rocksportrocker. Я читаю цей приклад і не можу зрозуміти, чому існує явний forцикл. Я, як правило, бачу паралельну процедуру приймати список і повертати список без циклу.
користувач1700890

20

Я виявив, що я також можу генерувати саме таку помилку на ідеально працюючому фрагменті коду, намагаючись використовувати на ньому профайлер.

Зауважте, що це було в Windows (де розгалуження трохи менш елегантне).

Я бігав:

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

І виявив, що видалення профілювання видалило помилку, а розміщення профілювання відновило її. Загнав мене батти, бо я знав, що код працював. Я перевіряв, чи щось оновив pool.py ... потім відчував тонус і усунув профілювання, і це було все.

Розміщення тут для архівів у випадку, якщо хтось інший натикається на нього.


3
WOW, дякую за згадку! Це загнало мене горіхами за останню годину або близько того; Я спробував усе до дуже простого прикладу - начебто нічого не вийшло. Але у мене також був профайл, який пройшов через мій пакетний файл :(
tim

1
О, не можу вам подякувати достатньо. Це звучить так нерозумно, як це несподівано. Я думаю, що це слід згадати в документах. Все, що у мене було, це імпорт pdb-імпорту, а проста функція верхнього рівня з просто a passне була "pickle".
0xc0de

10

Коли ця проблема виникає з multiprocessingпростим рішенням, це переключитися Poolна ThreadPool. Це можна зробити без зміни коду, окрім імпортного-

from multiprocessing.pool import ThreadPool as Pool

Це працює тому, що ThreadPool ділиться пам’яттю з основною темою, а не створює новий процес - це означає, що маринування не потрібно.

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

Тож якщо ви обробляєте багато користувачів в просторі користувачів python, це може бути не найкращим методом.


2
Але тоді ви використовуєте лише один процесор (принаймні, для звичайних версій Python, які використовують GIL ), який перемагає мету.
Endre Обидва

Це дійсно залежить від того, яка мета. Блокування глобального перекладача означає, що лише один екземпляр одночасно може запускати код python, але для дій, які сильно блокують (доступ до файлової системи, завантаження великих або декількох файлів, виконання зовнішнього коду), GIL в кінцевому підсумку не є проблемою. У деяких випадках накладні витрати від відкриття нових процесів (а не ниток) перевищують накладні витрати GIL.
tedivm

Це правда, дякую. Але все ж ви можете включити відповідь. У ці дні, коли зростає потужність обробки, в основному відбувається у вигляді більш потужних процесорних ядер, перехід від багатоядерного до одноядерного виконання є досить вагомим побічним ефектом.
Endre обидва

Хороший момент - я оновив відповідь більш детально. Я хочу зазначити, хоча переключення на багатопроцесорну передачу з потоком не робить функцію 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

Він також працює для нумерованих масивів.


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

Ця помилка також з’явиться, якщо у вас є будь-яка вбудована функція всередині об’єкта моделі, переданого в завдання async.

Тому переконайтесь, що перевірка моделей об'єктів , які передаються, не має вбудованих функцій. (У нашому випадку ми використовували 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.