Python関数をピクルする(またはコードをシリアル化する)簡単な方法はありますか?


100

(asyncoreを使用して)ネットワーク接続を介して関数を転送しようとしています。このような転送のためにPython関数(この場合は少なくとも、副作用がないもの)をシリアル化する簡単な方法はありますか?

理想的には、次のような機能のペアが欲しいです。

def transmit(func):
    obj = pickle.dumps(func)
    [send obj across the network]

def receive():
    [receive obj from the network]
    func = pickle.loads(s)
    func()

回答:


120

関数のバイトコードをシリアル化してから、呼び出し側で再構築できます。マーシャルモジュールは機能に再構築することができるシリアライズコードオブジェクトに使用することができます。つまり:

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.func_code)

次に、リモートプロセスで(code_stringを転送した後):

import marshal, types

code = marshal.loads(code_string)
func = types.FunctionType(code, globals(), "some_func_name")

func(10)  # gives 100

いくつかの警告:

  • marshalのフォーマット(つまり、Pythonバイトコード)は、Pythonのメジャーバージョン間で互換性がない場合があります。

  • cpython実装でのみ機能します。

  • 関数が、ピックアップする必要があるグローバル(インポートされたモジュール、他の関数などを含む)を参照する場合、これらもシリアル化するか、リモート側で再作成する必要があります。私の例では、リモートプロセスのグローバル名前空間を示しています。

  • おそらく、クロージャーやジェネレーター関数などのより複雑なケースをサポートするために、もう少し行う必要があります。


1
Python 2.5では、「新しい」モジュールは非推奨です。「new.function」は「types.FunctionType」に置き換える必要があります。「インポートタイプ」の後、私は信じています。
エリックOレビゴット2009

2
ありがとう。これはまさに私が探していたものです。いくつかの大まかなテストに基づいて、それはジェネレーターのように動作します。
マイケルフェアリー

2
marshalモジュールの最初の2つの段落を読んだ場合、代わりにpickleを使用することを強くお勧めします。ピクルスのページも同じです。docs.python.org/2/library/marshal.html
dgorissen

1
marshalとして初期化された辞書の辞書をシリアル化するモジュールを適用しようとしていますdefaultdict(lambda : defaultdict(int))。しかし、それはエラーを返しますValueError: unmarshallable object。私はpython2.7を使用していることに注意してください。何か案が?ありがとう
user17375

2
Python 3.5.3では、foo.func_codeが発生しAttributeErrorます。関数コードを取得する別の方法はありますか?
AlQuemist、

41

Pythonのpickleライブラリを拡張して、関数を含むさまざまな型をサポートするDillを確認してください。

>>> import dill as pickle
>>> def f(x): return x + 1
...
>>> g = pickle.dumps(f)
>>> f(1)
2
>>> pickle.loads(g)(1)
2

また、関数のクロージャー内のオブジェクトへの参照もサポートしています。

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3

2
dillは、オブジェクトのピクルス処理よりもソースコードを好む場合は、関数とラムダからソースコードを取得してディスクに保存することもできます。
Mike McKerns、2014年

14

Pyroこれ行うことができます


この特定のプロジェクトでは、標準ライブラリを使用する必要があります。
マイケルフェアリー

21
しかし、それは、それがどのように行われるかを確認するためにPyroのコードを見ることができないことを意味しません:)
Aaron Digulla

4
@ AaronDigulla- true、ただし、他の誰かが公開したコードの1行を読む前に、常にソフトウェアのライセンスを確認する必要があることを言及する価値があります。他人のコードを読んで、ソースを引用したり、ライセンス/コピーの制約を遵守したりせずにアイデアを再利用することは、多くの場合、盗用や著作権違反と見なされる可能性があります。
mdscruggs 2013

12

最も簡単な方法は、おそらくinspect.getsource(object)inspectモジュールを参照)、関数またはメソッドのソースコードを含む文字列を返すことです。


関数名がコードで明示的に定義されていることを除いて、これは見栄えがよく、少し問題があります。コードの最初の行を削除することもできますが、 'def \ / n func():'のような処理を行うと簡単に解決できます。関数の名前を関数自体でピクルすることもできますが、名前が衝突しないという保証はありません。または、関数をラッパーに配置する必要がありますが、これはまだ最もクリーンな解決策ではありませんが、それはする必要があるかもしれません。
マイケルフェアリー

1
検査モジュールは実際には、それが定義された場所の関数を要求し、ソースコードファイルからそれらの行を読み取るだけであることに注意してください。
phpが多すぎる

1
関数の名前は、.__ name__属性を使用して確認できます。あなたは、正規表現は^ DEF \ sの* {名前} \ sの*に置き換える(と好きな名前にそれにあなたを与える行うことができますそれは誰にでもありませんが、それはほとんどのもののために働くだろう。。
あまりにも多くのPHP

6

すべては、実行時に関数を生成するかどうかによって異なります。

そうした場合- inspect.getsource(object)動的に生成された関数はオブジェクトのソースを.pyファイルから取得するため機能せず、実行前に定義された関数のみをソースとして取得できます。

とにかく関数がファイルに配置されている場合は、レシーバーにそれらの関数へのアクセスを許可し、モジュール名と関数名のみを渡すようにしてください。

私が考えることができる動的に作成された関数の唯一の解決策は、送信前に文字列として関数を構築し、ソースを送信しeval()てから、受信側でそれを実行することです。

編集:marshalソリューションはかなりスマートに見えますが、組み込みのもの以外をシリアル化できることを知りませんでした



2
code_string = '' '
def foo(x):
    x * 2を返す
def bar(x):
    x ** 2を返す
'' '

obj = pickle.dumps(code_string)

exec(pickle.loads(obj))

foo(1)
> 2
バー(3)
> 9

2

あなたはこれを行うことができます:

def fn_generator():
    def fn(x, y):
        return x + y
    return fn

これで、モジュール名への参照transmit(fn_generator())ではfn(x,y)なく、実際の定義が送信されます。

同じトリックを使用して、ネットワーク経由でクラスを送信できます。


1

このモジュールで使用される基本的な関数はクエリをカバーし、さらにネットワーク全体で最高の圧縮を実現します。有益なソースコードを見てください:

y_serial.pyモジュール:: SQLiteでPythonオブジェクトをウェアハウス

"シリアライゼーション+パーシスタンス::数行のコードで、PythonオブジェクトをSQLiteに圧縮して注釈を付けます。その後、SQLを使用せずに、キーワードで年代順に取得します。データベースがスキーマレスデータを格納するための最も便利な「標準」モジュールです。"

http://yserial.sourceforge.net


1

Cloudpickleはおそらくあなたが探しているものです。Cloudpickleは次のように説明されます。

cloudpickleは、Pythonコードがネットワークを介して出荷され、データに近いリモートホストで実行されるクラスターコンピューティングに特に役立ちます。

使用例:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)

0

以下は、関数をラップしてピックル可能にするために使用できるヘルパークラスです。すでに言及されている警告marshalが適用されますが、可能な場合はいつでもピクルスを使用するように努めます。シリアライゼーション全体でグローバルまたはクロージャを保持するための取り組みは行われません。

    class PicklableFunction:
        def __init__(self, fun):
            self._fun = fun

        def __call__(self, *args, **kwargs):
            return self._fun(*args, **kwargs)

        def __getstate__(self):
            try:
                return pickle.dumps(self._fun)
            except Exception:
                return marshal.dumps((self._fun.__code__, self._fun.__name__))

        def __setstate__(self, state):
            try:
                self._fun = pickle.loads(state)
            except Exception:
                code, name = marshal.loads(state)
                self._fun = types.FunctionType(code, {}, name)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.