หลามมัลติโพรเซสซิง PicklingError: ไม่สามารถดอง <type 'function'>


244

ฉันขอโทษที่ฉันไม่สามารถทำซ้ำข้อผิดพลาดด้วยตัวอย่างที่ง่ายขึ้นและรหัสของฉันซับซ้อนเกินกว่าจะโพสต์ ถ้าฉันรันโปรแกรมใน IPython shell แทนที่จะเป็น 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 :( ทุกคนมีการจัดการเพื่อเรียกใช้รหัสหลามในการประมวลผลแบบขนาน 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วิธีการทั้งหมดใช้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 ต้องใส่บางสิ่งใน Queue ซึ่งไม่สามารถเลือกได้ มันอาจเป็นฟังก์ชั่นหรือข้อโต้แย้ง หากต้องการทราบข้อมูลเพิ่มเติมเกี่ยวกับปัญหาฉันขอแนะนำให้ทำสำเนาโปรแกรมของคุณและเริ่มประกอบมันทำให้ง่ายขึ้นและง่ายขึ้นทุกครั้งที่รันโปรแกรมอีกครั้งเพื่อดูว่าปัญหายังคงอยู่หรือไม่ เมื่อมันกลายเป็นเรื่องง่ายคุณจะค้นพบปัญหาด้วยตัวคุณเองหรือมีบางสิ่งที่คุณสามารถโพสต์ได้ที่นี่
unutbu

3
นอกจากนี้: หากคุณกำหนดฟังก์ชั่นที่ระดับบนสุดของโมดูล แต่ได้รับการตกแต่งแล้วการอ้างอิงจะเป็นการส่งออกของมัณฑนากรและคุณจะได้รับข้อผิดพลาดนี้ต่อไป
bobpoekert

5
เพียง 5 ปีที่ผ่านมา แต่ฉันเพิ่งพบสิ่งนี้ ปรากฎว่า "ระดับสูงสุด" จะต้องใช้ตัวอักษรมากกว่าปกติ: ดูเหมือนว่าสำหรับฉันแล้วคำจำกัดความของฟังก์ชั่นจะต้องนำหน้าการเริ่มต้นของพูล (เช่นpool = Pool()บรรทัดที่นี่ ) ฉันไม่ได้คาดหวังและนี่อาจเป็นสาเหตุที่ปัญหาของ OP ยังคงอยู่
Andras Deak

4
โดยเฉพาะอย่างยิ่งฟังก์ชั่นสามารถเลือกได้เฉพาะในกรณีที่กำหนดไว้ที่ระดับบนสุดของโมดูล ปรากฏว่าผลลัพธ์ของการนำfunctool.partialไปใช้กับฟังก์ชั่นระดับบนสุดยังสามารถดองได้แม้ว่ามันจะถูกกำหนดไว้ในฟังก์ชั่นอื่น
1071847

96

ฉันต้องการใช้แทน pathos.multiprocesssing เป็นทางแยกของใช้ที่ สามารถทำให้เป็นอนุกรมได้เกือบทุกอย่างในงูใหญ่ดังนั้นคุณจึงสามารถส่งมากขึ้นในแบบคู่ขนาน ส้อมนอกจากนี้ยังมีความสามารถในการทำงานโดยตรงกับฟังก์ชั่นการโต้แย้งหลายอย่างที่คุณต้องการสำหรับวิธีการเรียนmultiprocessingpathos.multiprocessingmultiprocessingdilldillpathos

>>> 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 ฉันจะไม่ติดตั้งแพ็กเกจหลามด้วยsudo(จากแหล่งภายนอกเช่น github โดยเฉพาะ) ฉันขอแนะนำให้ทำงานแทน:pip install --user git+...
Chris

การใช้งานเพียงอย่างเดียว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
Mike McKerns

3
@DanielGoldfarb: multiprocessเป็นทางแยกmultiprocessingที่dillมีการแทนที่pickleในหลายสถานที่ในรหัส ... แต่เป็นหลักนั่นคือมัน pathosให้ API เลเยอร์เพิ่มเติมบางส่วนmultiprocessและยังมีแบ็กเอนด์เพิ่มเติม แต่นั่นคือส่วนสำคัญของมัน
Mike McKerns

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()

6
ผมdillและpathosผู้เขียน ... และในขณะที่คุณมีสิทธิไม่ได้ดีกว่ามากและทำความสะอาดและมีความยืดหยุ่นมากขึ้นในการใช้งานนอกจากนี้ยังมีpathosเช่นเดียวกับในคำตอบของฉัน? หรือฉันอาจจะลำเอียงนิดหน่อย…
Mike McKerns

4
ฉันไม่ทราบเกี่ยวกับสถานะของpathosเวลาที่เขียนและต้องการเสนอวิธีแก้ปัญหาซึ่งใกล้กับคำตอบมาก ตอนนี้ฉันได้เห็นวิธีการแก้ปัญหาของคุณแล้วฉันเห็นด้วยว่านี่คือวิธีที่จะไป
rockportrocker

ฉันอ่านคำตอบของคุณแล้วก็เป็นไปอย่างDoh… I didn't even think of doing it like that. นั้นมันช่างยอดเยี่ยม
Mike McKerns

4
ขอขอบคุณสำหรับการโพสต์ฉันใช้วิธีนี้ในการโต้แย้งการลบล้าง / การเลิกใช้ซึ่งไม่สามารถเลือกได้: stackoverflow.com/questions/27883574/…
jazzblue

@rocksportrocker ฉันกำลังอ่านตัวอย่างนี้และไม่เข้าใจว่าทำไมมีforลูปที่ชัดเจน โดยปกติฉันจะเห็นรูทีนแบบขนานนำรายการและส่งคืนรายการโดยไม่มีการวนซ้ำ
user1700890

20

ฉันพบว่าฉันยังสามารถสร้างผลลัพธ์ข้อผิดพลาดที่แน่นอนบนชิ้นงานที่สมบูรณ์แบบของรหัสโดยพยายามใช้ profiler ในมัน

โปรดทราบว่านี่เป็นบน Windows (ซึ่งการฟอร์กมีความสวยงามน้อยกว่าเล็กน้อย)

ฉันกำลังวิ่ง:

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

และพบว่าการลบโปรไฟล์ลบข้อผิดพลาดและวางโปรไฟล์เรียกคืน กำลังขับรถฉันบ้าเหมือนกันเพราะฉันรู้รหัสที่ใช้ในการทำงาน ฉันกำลังตรวจสอบเพื่อดูว่ามีบางสิ่งบางอย่างที่อัปเดต pool.py ... แล้วมีความรู้สึกจมและตัดการทำโปรไฟล์และนั่นก็เป็นเช่นนั้น

การโพสต์ที่นี่เพื่อเก็บถาวรในกรณีที่มีคนอื่นเข้ามา


3
ว้าวขอบคุณที่พูดถึง! มันทำให้ฉันถั่วในชั่วโมงสุดท้ายหรือดังนั้น; ฉันลองทุกอย่างจนเป็นตัวอย่างง่ายๆ - ดูเหมือนว่าไม่มีอะไรทำงาน แต่ฉันยังมีผู้ให้ข้อมูลที่ทำงานผ่าน batchfile ของฉัน :(
ทิม

1
โอ้ขอบคุณมากพอแล้ว มันฟังดูงี่เง่ามาก ฉันคิดว่ามันควรจะกล่าวถึงในเอกสาร ทั้งหมดที่ฉันมีคือคำสั่ง pdb ที่นำเข้าและฟังก์ชั่นระดับบนสุดที่เรียบง่ายที่มีเพียงแค่passไม่ใช่ 'pickle'able
0xc0de

10

เมื่อปัญหานี้ขึ้นมาด้วยmultiprocessingวิธีง่ายๆคือการเปลี่ยนจากการPool ThreadPoolซึ่งสามารถทำได้โดยไม่ต้องเปลี่ยนรหัสอื่นนอกจากการนำเข้า -

from multiprocessing.pool import ThreadPool as Pool

ใช้งานได้เนื่องจาก ThreadPool ใช้หน่วยความจำร่วมกับเธรดหลักแทนที่จะสร้างกระบวนการใหม่ซึ่งหมายความว่าไม่จำเป็นต้องมีการดอง

ข้อเสียของวิธีนี้คือ python ไม่ใช่ภาษาที่ดีที่สุดในการจัดการกับเธรด - มันใช้สิ่งที่เรียกว่า Global Interpreter Lock เพื่อรักษาความปลอดภัยของเธรดซึ่งอาจทำให้การใช้งานช้าลงในบางกรณี อย่างไรก็ตามหากคุณมีปฏิสัมพันธ์กับระบบอื่น ๆ เป็นหลัก (การใช้คำสั่ง HTTP การพูดคุยกับฐานข้อมูลการเขียนไปยังระบบไฟล์) รหัสของคุณก็ไม่ได้ผูกกับซีพียูและจะไม่กระทบอะไรมาก ในความเป็นจริงฉันพบเมื่อเขียน HTTP / HTTPS เบนช์มาร์กที่โมเดลเธรดที่ใช้ที่นี่มีค่าใช้จ่ายและความล่าช้าน้อยลงเนื่องจากโอเวอร์เฮดจากการสร้างกระบวนการใหม่นั้นสูงกว่าโอเวอร์เฮดสำหรับสร้างเธรดใหม่

ดังนั้นหากคุณกำลังประมวลผลข้อมูลจำนวนมากในพื้นที่ผู้ใช้หลามนี่อาจไม่ใช่วิธีที่ดีที่สุด


2
แต่คุณใช้ CPU เพียงตัวเดียว (อย่างน้อยกับรุ่น Python ทั่วไปที่ใช้GIL ) ซึ่งมีวัตถุประสงค์เพื่อเอาชนะ
Endre ทั้ง

นั่นขึ้นอยู่กับว่าจุดประสงค์คืออะไร Global Interpreter Lock หมายความว่ามีเพียงหนึ่งอินสแตนซ์ต่อครั้งเท่านั้นที่สามารถเรียกใช้รหัสหลามได้ แต่สำหรับการกระทำที่ปิดกั้นอย่างมาก (การเข้าถึงระบบไฟล์การดาวน์โหลดไฟล์ขนาดใหญ่หรือหลายไฟล์การเรียกใช้รหัสภายนอก) GIL จะกลายเป็นปัญหา ในบางกรณีค่าโสหุ้ยจากการเปิดกระบวนการใหม่ (แทนที่จะเป็นเธรด) มีค่ามากกว่าค่า GIL
tedivm

นั่นเป็นเรื่องจริงขอบคุณ ยังคุณอาจต้องการรวม caveat ในคำตอบ ทุกวันนี้เมื่อพลังการประมวลผลเพิ่มขึ้นส่วนใหญ่จะมาในรูปแบบของแกนประมวลผลที่ทรงพลังมากกว่าการเปลี่ยนจากการมัลติคอร์ไปเป็นการประมวลผลแบบ Single-Core นั้นเป็นผลข้างเคียงที่ค่อนข้างสำคัญ
ทั้งสอง

จุดดีฉันได้อัปเดตคำตอบพร้อมรายละเอียดเพิ่มเติมแล้ว ฉันต้องการชี้ให้เห็นว่าการสลับไปใช้การประมวลผลแบบมัลติเธรดไม่ทำให้ 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

ข้อผิดพลาดนี้จะเกิดขึ้นหากคุณมีฟังก์ชั่น inbuilt ภายในโมเดลวัตถุที่ส่งผ่านไปยังงาน async

ดังนั้นตรวจสอบให้แน่ใจว่าได้ตรวจสอบวัตถุจำลองที่ผ่านไปแล้วว่าไม่มีฟังก์ชั่น inbuilt (ในกรณีของเราเราใช้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)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.