बहुप्रतिष्ठित पूल.मैप का उपयोग करते समय <टाइप करें 'Instmethod' को अचार नहीं कर सकते ()


218

मैं उपयोग करने के लिए कोशिश कर रहा हूँ multiprocessingके 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, एक बाध्य विधि; तो अचार की वजह से अब THAT को अचार बनाने की कोशिश करता है - और जैसा कि आपने इसे बताया है: कॉल करके _pickle_method, पुनरावर्ती रूप से। OOइस तरह से कोड द्वारा आईई , आपने अनिवार्य रूप से अनंत पुनरावृत्ति की शुरुआत की है। मेरा सुझाव है कि स्टीवन के कोड पर वापस जाना (और उचित नहीं होने पर ऊ की वेदी पर पूजा करना: पायथन में कई चीजें सबसे अधिक कार्यात्मक तरीके से की जाती हैं, और यह एक है)।
एलेक्स मार्टेली




74

ये सभी समाधान बदसूरत हैं क्योंकि जब तक आप मानक पुस्तकालय के बाहर नहीं जाते हैं, तब तक मल्टीप्रोसेसिंग और अचार टूटना और सीमित होता है।

यदि आप multiprocessingकहा जाता है की एक कांटा का उपयोग करें pathos.multiprocesssing, आप सीधे वर्गों और वर्ग विधियों का उपयोग मल्टीप्रोसेसिंग के mapकार्यों में कर सकते हैं। इसका कारण यह है है dillके बजाय प्रयोग किया जाता है pickleया cPickle, और dillअजगर में लगभग कुछ भी क्रमानुसार कर सकते हैं।

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.multiprocessing अब मौजूद नहीं है?
सहेल गोधने

10
मैं pathosलेखक हूँ । जिस संस्करण का आप जिक्र कर रहे हैं, वह कई साल पुराना है। GitHub पर संस्करण का प्रयास करें, आप उपयोग कर सकते हैं pathos.ppया github.com/uqfoundation/ppft
माइक मैककर्न्स

1
या github.com/uqfoundation/pathos । @ शेहेलोघेन: एक नई रिलीज लंबे समय से अधिक है, लेकिन जल्द ही बाहर होना चाहिए।
माइक मैकरनस

3
पहले pip install setuptools, फिर pip install git+https://github.com/uqfoundation/pathos.git@master। इससे उचित निर्भरता मिलेगी। एक नई रिलीज लगभग तैयार है ... अब लगभग सब कुछ pathosभी खिड़कियों पर चलता है, और 3.xसंगत है।
माइक मैककर्नस

1
@ रीका: हाँ। अवरुद्ध, पुनरावृत्ति, और async नक्शे उपलब्ध हैं।
माइक मैकरनस

35

आप __call__()अपने अंदर एक विधि को भी परिभाषित कर सकते हैं someClass(), जो कॉल करता है someClass.go()और फिर someClass()पूल का एक उदाहरण देता है । यह ऑब्जेक्ट पिकलेबल है और यह ठीक काम करता है (मेरे लिए) ...


3
एलेक्स मार्टेली द्वारा प्रस्तावित तकनीक की तुलना में यह बहुत आसान है, लेकिन आप अपने मल्टीप्रोसेसिंग पूल में प्रति वर्ग केवल एक विधि भेजने के लिए सीमित हैं।
पदावनत

6
ध्यान में रखने के लिए एक और विस्तार यह है कि यह केवल ऑब्जेक्ट (क्लास इंस्टेंस) है जो क्लास को नहीं, बल्कि पिक हो जाता है। इसलिए, यदि आपने अपने डिफ़ॉल्ट मूल्यों से किसी भी वर्ग की विशेषताओं को बदल दिया है, तो ये परिवर्तन विभिन्न प्रक्रियाओं के लिए प्रचारित नहीं करेंगे। वर्कअराउंड यह सुनिश्चित करने के लिए है कि आपके फ़ंक्शन की ज़रूरत की हर चीज़ एक इंस्टेंस विशेषता के रूप में संग्रहीत है।
पदावनत

2
@ डोरवाक क्या आप इसके साथ एक सरल उदाहरण दिखा सकते हैं __call__()? मुझे लगता है कि आपका उत्तर क्लीनर हो सकता है - मैं इस त्रुटि को समझने के लिए संघर्ष कर रहा हूं, और पहली बार मैं कॉल देखने के लिए आया हूं। वैसे, यह उत्तर यह स्पष्ट करने में भी मदद करता है कि मल्टीप्रोसेसिंग क्या करता है: [ stackoverflow.com/a/20789937/305883]
user305883

1
क्या आप इसका उदाहरण दे सकते हैं?
Frmsaul

1
इसके लिए उदाहरण कोड के साथ एक नया उत्तर (वर्तमान में नीचे एक) पोस्ट किया गया है।
एरोन

22

स्टीवन बेथर्ड के समाधान के लिए कुछ सीमाएँ हालांकि:

जब आप अपनी कक्षा विधि को एक फ़ंक्शन के रूप में पंजीकृत करते हैं, तो आपकी विधि प्रसंस्करण समाप्त होने पर हर बार आपकी कक्षा के विध्वंसक को आश्चर्यजनक रूप से बुलाया जाता है। इसलिए यदि आपके पास अपनी कक्षा का 1 उदाहरण है जो n की विधि को कॉल करता है, तो सदस्य 2 रन के बीच गायब हो सकते हैं और आपको एक संदेश मिल सकता है 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)
torek

1
@Eric मैं एक ही त्रुटि हो रही थी और मैं इस समाधान की कोशिश की, लेकिन मैं के रूप में नए त्रुटि मिल रही शुरू कर दिया "cPickle.PicklingError: अचार नहीं कर सकते <प्रकार 'समारोह'>: विशेषता देखने builtin .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

ऊपर parisjohn से समाधान मेरे साथ ठीक काम करता है। प्लस कोड साफ और समझने में आसान लगता है। मेरे मामले में पूल का उपयोग करके कॉल करने के लिए कुछ कार्य हैं, इसलिए मैंने parisjohn के कोड को थोड़ा नीचे संशोधित किया। मैंने फोन कई कार्य कॉल करने के लिए सक्षम होने के लिए, और समारोह के नाम से तर्क dict में पारित कर रहे हैं 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। यह मल्टीप्रोसेसिंग इंटरफ़ेस का एक थ्रेड आधारित कार्यान्वयन है, जो पायथन 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

अलग-अलग दुर्गंध का उपयोग क्यों नहीं करना चाहिए?

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

अद्यतन: इस लेखन के दिन के रूप में, नामपट्ट्स पिकेबल हैं (अजगर 2.7 के साथ शुरू)

यहाँ समस्या यह है कि बाल प्रक्रियाएँ ऑब्जेक्ट-इन के वर्ग को आयात करने में सक्षम नहीं हैं, इस मामले में, क्लास P-, मल्टी-मॉडल प्रोजेक्ट के मामले में, क्लास P को आयात योग्य होना चाहिए, कहीं भी बच्चे की प्रक्रिया का उपयोग नहीं किया जाना चाहिए।

एक त्वरित समाधान यह है कि इसे आयात करने के लिए इसे ग्लोबल्स () से प्रभावित किया जाए।

globals()["P"] = P
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.