PythonマルチプロセッシングPicklingError:<type 'function'>をピクルできません


243

簡単な例ではエラーを再現できず、コードが複雑すぎて投稿できません。通常のPythonではなくIPythonシェルでプログラムを実行すると、うまくいきます。

この問題に関する以前のメモを調べました。それらはすべて、クラス関数内で定義された関数を呼び出すためにプールを使用することによって引き起こされました。しかし、これは私には当てはまりません。

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を並列処理してPythonコードを実行できた人はいますか?
Chris

回答:


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ピック可能であるため、ピック可能であることに注意してください。Foofoo.__dict__


2
お返事をありがとうございます。質問を更新しました。私はそれが原因だとは思わない
Vendetta

7
PicklingErrorを取得するには、pickleできないキューに何かを置く必要があります。関数またはその引数の可能性があります。問題の詳細を調べるには、プログラムのコピーを作成し、問題を解決するために、プログラムを再実行するたびに問題を解決するためにプログラムを再実行することをお勧めします。それが本当に簡単になったら、自分で問題を発見したか、ここに投稿できるものがあるでしょう。
unutbu

3
また、モジュールのトップレベルで関数を定義し、それが装飾されている場合、参照はデコレータの出力になり、とにかくこのエラーが発生します。
bobpoekert 2013

5
たったの5年遅れですが、これに遭遇しました。「トップレベル」は通常よりも文字どおりに解釈する必要があることがわかります。関数の定義はプール初期化(つまり、ここpool = Pool()行)の前に行う必要があるようです。私はそれを期待していなかった、そしてこれがOPの問題が続いた理由かもしれない。
Andras Deak 2017年

4
特に、関数はモジュールのトップレベルで定義されている場合にのみピクル可能です。functool.partialトップレベル関数 に適用した結果は、別の関数内で定義されていても、ピクル可能であるように見えます。
user1071847

96

pathos.multiprocesssing代わりにを使用しますmultiprocessingpathos.multiprocessingmultiprocessingその用途のフォークですdilldillPythonのほとんどすべてをシリアル化できるため、より多くのデータを並行して送信できます。また、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@mastersudo pip install git+https://github.com/uqfoundation/pathos.git@master
アレクサンダーマクファーレン

5
@AlexanderMcFarlane私はsudo(特にgithubなどの外部ソースから)Pythonパッケージをインストールしません。代わりに、私は実行することをお勧めします: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今は動作し、pathosPython 3互換です。
マイクマッカーンズ2016

3
@DanielGoldfarbは:multiprocessのフォークですmultiprocessingどこdillに置き換えたpickleコード内のいくつかの場所で...しかし、本質的に、それはそれです。 pathosには追加のAPIレイヤーがいくつかmultiprocessあり、追加のバックエンドもあります。しかし、それはそれの要点です。
Mike McKerns

29

他の人が言ったように、multiprocessingPythonオブジェクトは、ピクル化できるワーカープロセスにのみ転送できます。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
私はだdillpathos著者...とあなたが正しいですが、それはまた、使用するので、非常に良くしてクリーンで、より柔軟ではないpathos私の答えのように?あるいは、私は少し偏っています…
マイクマッカーンズ2014年

4
pathos執筆時の状況を知らず、答えに非常に近い解決策を提示したいと思いました。私はあなたの解決策を見たので、これが進むべき道であることに同意します。
rocksportrocker 14年

私はあなたの解決策を読んだDoh… I didn't even think of doing it like that. ので、それはちょっとクールでした。
Mike McKerns、2014年

4
:投稿のためのおかげで、私は漬けすることができませんでしたディリング/ undilling引数のためにこのアプローチを使用stackoverflow.com/questions/27883574/...
jazzblue

@rocksportrocker。私はこの例を読んでいて、なぜ明示的なforループがあるのか​​理解できません。私は通常、並列ルーチンがリストを取り、ループなしでリストを返すのを見ました。
user1700890

20

プロファイラーを使用することで、完全に機能するコードでエラー出力を正確に生成できることもわかりました。

これはWindowsであったことに注意してください(フォークが少しエレガントではありません)。

私は走っていた:

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

そして、プロファイリングを削除するとエラーが削除され、プロファイリングを配置するとエラーが復元されることがわかりました。動作するコードを知っていたので、私もバティを運転していた。何かがpool.pyを更新したかどうかを確認していたところ、沈没感があり、プロファイリングが削除されました。

他の誰かがそれに遭遇した場合に備えて、アーカイブをここに投稿します。


3
すごい、言及してくれてありがとう!それは私に最後の時間かそこらを運転しました。私は非常に単純な例まですべてを試しました-何もうまくいかなかったようです。しかし、私はまた、バッチファイルを介してプロファイラーを実行しました:(
tim

1
ああ、ありがとうございます。予想外なので、これは馬鹿げているように聞こえます。私はそれがドキュメントで言及されるべきだと思います。私が持っていたのはimport pdbステートメントだけで、単純なトップレベルの関数だけでpass「ピックル」できませんでした。
0xc0de

10

この問題が発生した場合のmultiprocessing簡単な解決策は、からに切り替えるPoolことThreadPoolです。これは、インポート以外のコードを変更せずに実行できます。

from multiprocessing.pool import ThreadPool as Pool

これは、新しいプロセスを作成するのではなく、ThreadPoolがメインスレッドとメモリを共有するため機能します。つまり、酸洗いは必要ありません。

この方法の欠点は、Pythonがスレッドを処理する最も優れた言語ではないことです。ここでは、グローバルインタープリターロックと呼ばれるものを使用してスレッドセーフを維持します。ただし、主に他のシステムとやり取りしている場合(HTTPコマンドの実行、データベースとの通信、ファイルシステムへの書き込み)、コードはCPUの制約を受けないため、ヒットすることはあまりありません。実際、HTTP / HTTPSベンチマークを作成したときに、ここで使用されているスレッドモデルのオーバーヘッドと遅延が少ないことがわかりました。これは、新しいプロセスの作成によるオーバーヘッドが、新しいスレッドの作成によるオーバーヘッドよりもはるかに高いためです。

したがって、Pythonユーザー空間で大量のものを処理している場合、これは最良の方法ではない可能性があります。


2
ただし、使用しているCPUは1つだけです(少なくとも、GILを使用する通常のPythonバージョンでは)。これは、目的に反します。
Endre Both

それは本当に目的が何であるかに依存します。グローバルインタープリターロックは、一度に1つのインスタンスのみがPythonコードを実行できることを意味しますが、非常にブロックするアクション(ファイルシステムアクセス、大きなファイルまたは複数のファイルのダウンロード、外部コードの実行)の場合、GILは問題ではなくなります。(スレッドではなく)新しいプロセスを開くことによるオーバーヘッドがGILオーバーヘッドを上回る場合があります。
tedivm

それは本当です、ありがとう。それでも、答えに警告を含めることをお勧めします。最近の処理能力の向上は、ほとんどの場合、より強力なCPUコアというよりはむしろ、マルチコアからシングルコアの実行への切り替えはかなり重大な副作用です。
Endre Both

良い点-答えを詳細に更新しました。スレッド化されたマルチプロセッシングに切り替えても、Pythonが単一コアでのみ機能するわけではないことを指摘しておきます。
テディブ

4

このソリューションは、dillのインストールのみを必要とし、pathosとして他のライブラリは必要ありません。

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配列でも機能します。


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

このエラーは、非同期ジョブに渡されたモデルオブジェクト内に組み込み関数がある場合にも発生します。

したがって、渡されるモデルオブジェクトに組み込み関数がないことを確認してください。(私たちの場合、モデル内FieldTracker()django-model-utilsの関数を使用して特定のフィールドを追跡していました)。関連するGitHubの問題へのリンクは次のとおりです。


0

@rocksportrockerソリューションに基づいて構築されているため、結果を送信してRECVするときにディルを作成することは理にかなっています。

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.