Неможливо вибрати <type 'instancemethod'> під час використання багатопроцесорного Pool.map ()


218

Я намагаюся використовувати функцію multiprocessing's, Pool.map()щоб розділити роботу одночасно. Коли я використовую такий код, він працює добре:

import multiprocessing

def f(x):
    return x*x

def go():
    pool = multiprocessing.Pool(processes=4)        
    print pool.map(f, range(10))


if __name__== '__main__' :
    go()

Однак, коли я використовую його в більш об'єктно-орієнтованому підході, він не працює. Повідомлення про помилку, яке воно дає:

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

Це відбувається, коли моя основна програма:

import someClass

if __name__== '__main__' :
    sc = someClass.someClass()
    sc.go()

і наступний мій someClassклас:

import multiprocessing

class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(self.f, range(10))

Хтось знає, яка може бути проблема чи простий спосіб її вирішити?


4
якщо f - вкладена функція, є аналогічна помилкаPicklingError: Can't pickle <class 'function'>: attribute lookup builtins.function failed
ggg

Відповіді:


122

Проблема полягає в тому, що мультипроцесори повинні підбирати речі, щоб перетягувати їх між процесами, а пов'язані методи не підбираються. Вирішення проблеми (вважаєте ви це "легким" чи ні ;-) - додати інфраструктуру до вашої програми, щоб дозволити макіяж таких методів, зареєструвавши її за допомогою стандартного методу бібліотеки copy_reg .

Наприклад, внесок Стівена Бетхарда в цю нитку (до кінця потоку) показує один ідеально працездатний підхід, що дозволяє вибирати / відбирати метод через copy_reg.


Це чудово - дякую. Здається, якимось чином прогресував: у будь-якому випадку, використовуючи код на pastebin.ca/1693348, я отримую RuntimeError: максимальна глибина рекурсії перевищена. Я оглянувся, і один форум на форумі рекомендував збільшити максимальну глибину до 1500 (з 1000 за замовчуванням), але я там не мав радості. Якщо чесно, я не можу побачити, яка частина (щонайменше, мого коду) могла б повторюватися поза контролем, якщо тільки з якоїсь причини код не забирається і не відбирає в циклі через незначні зміни, які я вніс для того, щоб внести Код Стівена OO'd?
Вентолін

1
Ваші _pickle_methodприбутки self._unpickle_method, зв'язаний метод; тому, звичайно, маринований спробує підсолити ТО - і це робиться так, як ви йому сказали: зателефонувавши _pickle_method, рекурсивно. Тобто, OOвикористовуючи код таким чином, ви неминуче ввели нескінченну рекурсію. Я пропоную повернутися до коду Стівена (а не поклонятися біля вівтаря ОО, коли це не підходить: багато речей на Python найкраще робити більш функціонально, і це один).
Алекс Мартеллі


15
Для супер-ледачого дивіться єдину відповідь, яка заважала розміщувати фактичний
некерований

2
Інший спосіб виправити / обійти проблему травлення з допомогою кропу, побачити моя відповідь stackoverflow.com/questions/8804830 / ...
rocksportrocker

74

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

Якщо ви використовуєте вилку multiprocessingвиклику pathos.multiprocesssing, ви можете безпосередньо використовувати класи та методи класу у mapфункціях багатопроцесорної обробки . Це тому dill, що використовується замість pickleабо cPickle, і dillможе серіалізувати майже що завгодно в python.

pathos.multiprocessingтакож надає функцію асинхронної карти ... і вона може mapфункціонувати з декількома аргументами (наприклад map(math.pow, [1,2,3], [4,5,6]))

Дивіться: Що можна зробити для багатопроцесорної обробки та кропу разом?

і: http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization/

>>> import pathos.pools as pp
>>> p = pp.ProcessPool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]

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

>>> import pathos.pools as pp
>>> class someClass(object):
...   def __init__(self):
...     pass
...   def f(self, x):
...     return x*x
...   def go(self):
...     pool = pp.ProcessPool(4)
...     print pool.map(self.f, range(10))
... 
>>> sc = someClass()
>>> sc.go()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> 

Отримати код можна тут: https://github.com/uqfoundation/pathos


3
Чи можете ви, будь ласка, оновити цю відповідь на основі pathos.pp, тому що патологічний обробка більше не існує?
Сахель Годхане

10
Я pathosавтор. Версія, на яку ви посилаєтесь, вже кілька років. Спробуйте версію на github, Ви можете використовувати pathos.ppабо github.com/uqfoundation/ppft .
Майк Маккернс

1
або github.com/uqfoundation/pathos . @SaheelGodhane: Новий випуск давно назрів, але незабаром його вийде.
Майк Маккернс

3
Спочатку pip install setuptools, потім pip install git+https://github.com/uqfoundation/pathos.git@master. Це отримає відповідні залежності. Новий випуск майже готовий ... зараз майже все pathosтакож працює на Windows, і він 3.xсумісний.
Майк Маккернс

1
@Rika: Так. Доступні карти блокування, ітерації та асинхронізації.
Майк Маккернс

35

Ви також можете визначити __call__()метод всередині вашого someClass(), який викликає someClass.go()та передає екземпляр someClass()до пулу. Цей об'єкт є вибірковим і він чудово працює (для мене) ...


3
Це набагато простіше, ніж техніка, запропонована Алексом Мартеллі, але ви обмежуєтесь надсиланням лише одного методу за клас до вашого багатопроцесорного пулу.
застарілий

6
Ще однією деталлю, яку слід пам’ятати, є те, що макіяж вибирається лише об’єктом (екземпляром класу), а не самим класом. Тому, якщо ви змінили будь-які атрибути класу зі своїх значень за замовчуванням, ці зміни не поширюватимуться на різні процеси. Вирішення проблеми полягає в тому, щоб переконатися, що все, що потрібно вашій функції, зберігається як атрибут примірника.
застарілий

2
@dorvak, чи не могли б ви показати простий приклад __call__()? Я думаю, що ваша відповідь може бути більш чіткою - я намагаюся зрозуміти цю помилку, і вперше приходжу до дзвінка. До речі, також ця відповідь допомагає з’ясувати, що робить багатопроцесорна робота: [
stackoverflow.com/a/20789937/305883

1
Чи можете ви навести приклад цього?
frmsaul

1
Існує новий відповідь відправив ( в даний час нижче цього) з прикладами коду для цього.
Аарон

22

Деякі обмеження, що стосуються рішення Стівена Бетхарда:

Коли ви реєструєте метод класу як функцію, деструктор вашого класу дивно викликається щоразу, коли обробка вашого методу закінчується. Отже, якщо у вас є 1 екземпляр вашого класу, який викликає n разів більше його методу, члени можуть зникати між двома запусками, і ви можете отримати повідомлення malloc: *** error for object 0x...: pointer being freed was not allocated(наприклад, відкритий файл-член) або pure virtual method called, terminate called without an active exception(що означає, що термін експлуатації об'єкта-члена, який я використав, був коротшим, ніж що я думав). Це я отримав, коли маю справу з n більшим, ніж розмір пулу. Ось короткий приклад:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

# --------- see Stenven's solution above -------------
from copy_reg import pickle
from types import MethodType

def _pickle_method(method):
    func_name = method.im_func.__name__
    obj = method.im_self
    cls = method.im_class
    return _unpickle_method, (func_name, obj, cls)

def _unpickle_method(func_name, obj, cls):
    for cls in cls.mro():
        try:
            func = cls.__dict__[func_name]
        except KeyError:
            pass
        else:
            break
    return func.__get__(obj, cls)


class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multi-processing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self.process_obj, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __del__(self):
        print "... Destructor"

    def process_obj(self, index):
        print "object %d" % index
        return "results"

pickle(MethodType, _pickle_method, _unpickle_method)
Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once)

Вихід:

Constructor ...
object 0
object 1
object 2
... Destructor
object 3
... Destructor
object 4
... Destructor
object 5
... Destructor
object 6
... Destructor
object 7
... Destructor
... Destructor
... Destructor
['results', 'results', 'results', 'results', 'results', 'results', 'results', 'results']
... Destructor

__call__Метод не такий еквівалент, тому що [Ні, ...] НЕ зчитуються з результатів:

from multiprocessing import Pool, cpu_count
from multiprocessing.pool import ApplyResult

class Myclass(object):

    def __init__(self, nobj, workers=cpu_count()):

        print "Constructor ..."
        # multiprocessing
        pool = Pool(processes=workers)
        async_results = [ pool.apply_async(self, (i,)) for i in range(nobj) ]
        pool.close()
        # waiting for all results
        map(ApplyResult.wait, async_results)
        lst_results=[r.get() for r in async_results]
        print lst_results

    def __call__(self, i):
        self.process_obj(i)

    def __del__(self):
        print "... Destructor"

    def process_obj(self, i):
        print "obj %d" % i
        return "result"

Myclass(nobj=8, workers=3)
# problem !!! the destructor is called nobj times (instead of once), 
# **and** results are empty !

Тож жоден з обох методів не задовольняє ...


7
Ви Noneповертаєтесь, тому що у вашому визначенні __call__відсутнє return: так і має бути return self.process_obj(i).
торек

1
@Eric Я отримував таку ж помилку, і я спробував це рішення, однак я почав отримувати нову помилку як "cPickle.PicklingError: Не можу вибрати <type 'function'>: вбудований пошук атрибута. Функція не вдалася". Чи знаєте ви, що може бути причиною цього?
Наман

15

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

Як усі кажуть, проблема полягає в тому, що multiprocessingкод повинен підбирати речі, які він надсилає до запущених підпроцесів, і вибирач не використовує екземпляри-методи.

Однак замість того, щоб надіслати метод-примірник, ви можете надіслати фактичний екземпляр класу плюс ім'я функції для виклику звичайній функції, яка потім використовує getattrдля виклику методу-примірника, створюючи таким чином зв'язаний метод у Poolпідпроцесі. Це схоже на визначення __call__методу, за винятком того, що ви можете викликати кілька функцій-членів.

Викрасти код @ EricH. з його відповіді та трохи анотувати його (я повторно вписав його, отже, всі зміни назви, і таке, чомусь, здавалося, простіше, ніж вирізати і вставити :-)) для ілюстрації всієї магії:

import multiprocessing
import os

def call_it(instance, name, args=(), kwargs=None):
    "indirect caller for instance methods and multiprocessing"
    if kwargs is None:
        kwargs = {}
    return getattr(instance, name)(*args, **kwargs)

class Klass(object):
    def __init__(self, nobj, workers=multiprocessing.cpu_count()):
        print "Constructor (in pid=%d)..." % os.getpid()
        self.count = 1
        pool = multiprocessing.Pool(processes = workers)
        async_results = [pool.apply_async(call_it,
            args = (self, 'process_obj', (i,))) for i in range(nobj)]
        pool.close()
        map(multiprocessing.pool.ApplyResult.wait, async_results)
        lst_results = [r.get() for r in async_results]
        print lst_results

    def __del__(self):
        self.count -= 1
        print "... Destructor (in pid=%d) count=%d" % (os.getpid(), self.count)

    def process_obj(self, index):
        print "object %d" % index
        return "results"

Klass(nobj=8, workers=3)

Вихід показує, що, дійсно, конструктор викликається один раз (у оригінальному підручнику), а деструктор викликається 9 разів (один раз за кожну зроблену копію = 2 або 3 рази на процедуру пулу-працівника, якщо потрібно, плюс один раз в оригіналі процес). Це часто нормально, як і в цьому випадку, оскільки інструмент вибору за замовчуванням робить копію всього екземпляра і (напів-) таємно повторно заповнює його - у цьому випадку, роблячи:

obj = object.__new__(Klass)
obj.__dict__.update({'count':1})

- тому, хоча деструктор викликається вісім разів у трьох робочих процесах, він щоразу відлічує від 1 до 0, але, звичайно, ви все одно можете потрапити в цю проблему. При необхідності ви можете надати своє __setstate__:

    def __setstate__(self, adict):
        self.count = adict['count']

наприклад, у цьому випадку.


1
Це, безумовно, найкраща відповідь на цю проблему, оскільки це найпростіше застосувати до поведінки за замовчуванням, що не підходить для підбирання,
Метт Тейлор

12

Ви також можете визначити __call__()метод всередині вашого someClass(), який викликає someClass.go()та передає екземпляр someClass()до пулу. Цей об'єкт є вибірковим і він чудово працює (для мене) ...

class someClass(object):
   def __init__(self):
       pass
   def f(self, x):
       return x*x

   def go(self):
      p = Pool(4)
      sc = p.map(self, range(4))
      print sc

   def __call__(self, x):   
     return self.f(x)

sc = someClass()
sc.go()

3

Розчин з паришона вище працює добре зі мною. Плюс код виглядає чисто і легко зрозуміти. У моєму випадку є кілька функцій для виклику за допомогою Pool, тому я трохи змінив код parisjohn трохи нижче. Я здійснив виклик, щоб мати можливість викликати декілька функцій, а назви функцій передаються у диктаті аргументів від go():

from multiprocessing import Pool
class someClass(object):
    def __init__(self):
        pass

    def f(self, x):
        return x*x

    def g(self, x):
        return x*x+1    

    def go(self):
        p = Pool(4)
        sc = p.map(self, [{"func": "f", "v": 1}, {"func": "g", "v": 2}])
        print sc

    def __call__(self, x):
        if x["func"]=="f":
            return self.f(x["v"])
        if x["func"]=="g":
            return self.g(x["v"])        

sc = someClass()
sc.go()

1

Потенційно тривіальним рішенням цього є перехід на використання multiprocessing.dummy. Це потокова реалізація багатопроцесорного інтерфейсу, який, схоже, не має цієї проблеми в Python 2.7. Тут у мене немає великого досвіду, але ця швидка зміна імпорту дозволила мені зателефонувати apply_async за методом класу.

Кілька хороших ресурсів на тему multiprocessing.dummy:

https://docs.python.org/2/library/multiprocessing.html#module-multiprocessing.dummy

http://chriskiehl.com/article/parallelism-in-one-line/


1

У цьому простому випадку, коли someClass.fне успадковувати жодних даних із класу і не приєднувати нічого до класу, можливим рішенням було б відокремити f, тож його можна вибрати:

import multiprocessing


def f(x):
    return x*x


class someClass(object):
    def __init__(self):
        pass

    def go(self):
        pool = multiprocessing.Pool(processes=4)       
        print pool.map(f, range(10))

1

Чому б не використовувати окремий func?

def func(*args, **kwargs):
    return inst.method(args, kwargs)

print pool.map(func, arr)

1

Я зіткнувся з цим самим питанням, але виявив, що є кодер JSON, який можна використовувати для переміщення цих об'єктів між процесами.

from pyVmomi.VmomiSupport import VmomiJSONEncoder

Використовуйте це для створення свого списку:

jsonSerialized = json.dumps(pfVmomiObj, cls=VmomiJSONEncoder)

Потім у картографічній функції використовуйте це для відновлення об'єкта:

pfVmomiObj = json.loads(jsonSerialized)

0

Оновлення: станом на день написання цього тексту, назване назвамиTuples (починаючи з python 2.7)

Проблема тут полягає в тому, що дочірні процеси не в змозі імпортувати клас об'єкта - у цьому випадку клас P-, у випадку мультимодельного проекту клас P повинен бути імпортним у будь-якому місці, до якого використовується звичний дочірній процес.

швидке вирішення - зробити його вагомим, впливаючи на глобалістів ()

globals()["P"] = P
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.