22

この質問のフォローアップとして: Python関数をピクルスにする(またはコードをシリアル化する)簡単な方法はありますか?

上記の投稿からこの弾丸の例を見たいと思います:

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

マーシャルを使用して関数のバイトコードをファイルに書き込む簡単なテストがあります。

def g(self,blah): 
    print blah

def f(self):
    for i in range(1,5):
        print 'some function f'
        g('some string used by g')

data = marshal.dumps(f.func_code)

file = open('/tmp/f2.txt', 'w')
file.write(data)

次に、新しいpythonインスタンスを開始します。

file = open('/tmp/f2.txt', 'r')
code = marshal.loads(file.read())
func2 = types.FunctionType(code, globals(), "some_func_name");
func2('blah')

これにより、次のようになります。

NameError: global name 'g' is not defined

これは、gを含めるために私が行ったさまざまなアプローチとは無関係です。基本的にgをfと同じ方法で送信しようとしましたが、fはまだgを認識できません。gをグローバル名前空間に入れて、受信プロセスでfが使用できるようにするにはどうすればよいですか?

誰かがこれを行う方法の例としてpyroを見ることも勧めました。私はすでにディスコプロジェクトの関連コードを理解しようと試みました。私は彼らのdPickleクラスを受講し、スタンドアロンアプリでdisco / tests/test_pickle.py機能を再現しようとしましたが成功しませんでした。私の実験では、dumps呼び出しを使用して関数マーシャリングを実行する際に問題が発生しました。とにかく、次はパイロ探査かもしれません。

要約すると、私が求めている基本的な機能は、メソッドをネットワーク経由で送信し、すべての基本的な「ワークスペース」メソッドを送信できるようにすることです(gなど)。

回答からの変更の例:

動作中のfunction_writer:

import marshal, types

def g(blah): 
    print blah


def f():
    for i in range(1,5):
        print 'some function f'
        g('blah string used by g')


f_data = marshal.dumps(f.func_code)
g_data = marshal.dumps(g.func_code);

f_file = open('/tmp/f.txt', 'w')
f_file.write(f_data)

g_file = open('/tmp/g.txt', 'w')
g_file.write(g_data)

動作中のfunction_reader:

import marshal, types

f_file = open('/tmp/f.txt', 'r')
g_file = open('/tmp/g.txt', 'r')

f_code = marshal.loads(f_file.read())
g_code = marshal.loads(g_file.read())

f = types.FunctionType(f_code, globals(), 'f');
g = types.FunctionType(g_code, globals(), 'g');

f()
4

5 に答える 5

32

2020年9月更新: 以下の@ogriselによるコメントを参照してください。2013年にこの回答の元のバージョンを書いた直後に、PiCloudの開発者はDropboxに移行しましたが、7年後も多くの人々がまだcloudpickleモジュールを使用しています。モジュールはApacheSparkに移行し、そこで保守と改善が続けられています。それに応じて、以下の例と背景テキストを更新しています。

Cloudpickle

cloudpickleパッケージは、関数、メソッド、クラス、さらにはラムダ、さらには依存関係をピクルスにすることができますそれを試してみるには、ちょうどpip install cloudpickleその時:

import cloudpickle

def foo(x):
    return x*3

def bar(z):
    return foo(z)+1

x = cloudpickle.dumps(bar)
del foo
del bar

import pickle

f = pickle.loads(x)
print(f(3))  # displays "10"

つまり、電話をかけるcloudpickle.dump()cloudpickle.dumps()、使用するのと同じ方法pickle.*で、後でネイティブを使用するpickle.load()pickle.loads()、解凍します。

バックグラウンド

PiCcloud.comcloudはLGPLの下でpythonパッケージをリリースし、他のオープンソースプロジェクトはすぐにそれを使い始めました(cloudpickle.pyいくつか見るためにグーグル)。picloud.comの人々は、汎用のコードピクルスを機能させることに力を注ぐインセンティブを持っていました。彼らのビジネス全体はそれを中心に構築されていました。cpu_intensive_function()アイデアは、AmazonのEC2グリッドで実行したい場合は、次のものを置き換えるだけであるというものでした。

cpu_intensive_function(some, args) 

と:

cloud.call(cpu_intensive_function, some, args)

後者はcloudpickle、依存するコードとデータを取得し、EC2に送信して実行し、電話をかけたときに結果を返すために使用されていましたcloud.result()

Picloudはミリ秒単位で請求され、非常に安価でした。モンテカルロシミュレーションと財務時系列分析に常に使用していましたが、それぞれ数秒で数百のCPUコアが必要でした。数年後、私はまだそれについて十分に良いことを言うことができず、そこで働くことさえしませんでした。

于 2013-06-03T06:35:41.757 に答える
6

基本的にgをfと同じ方法で送信しようとしましたが、fはまだgを認識できません。gをグローバル名前空間に入れて、受信プロセスでfが使用できるようにするにはどうすればよいですか?

グローバル名に割り当てgます。(に割り当てているのではなく、に割り当てffunc2いるfようです。でそのようなことをしている場合g、なぜf見つからないのかは明らかgです。名前解決は実行時に行われることを忘れないでください。g電話をかけるまで検索されませんf。)

もちろん、これを行うために使用しているコードを表示しなかったので、私は推測しています。

選択を解除する関数のグローバル名前空間(サンドボックス)に使用する別のディクショナリを作成するのが最適な場合があります。そうすれば、それらのすべてのグローバル変数は、これを実行しているモジュールから分離されます。したがって、次のようなことを行うことができます。

sandbox = {}

with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        sandbox[code.co_name] = types.FunctionType(code, sandbox, code.co_name)

この例では、すべての関数のコードオブジェクトを次々に1つのファイルに入れ、それらを読み込むときにコードオブジェクトの名前を取得し、それを両方の関数オブジェクトの名前の基礎として使用すると仮定します。サンドボックス辞書に保存されている名前。

選択されていない関数の内部では、サンドボックスディクショナリがそれらの関数であるglobals()ため、内部f()では、gからその値を取得しますsandbox["g"]。呼び出すfには、次のようになります。sandbox["f"]("blah")

于 2012-04-06T19:23:35.617 に答える
4

すべてのモジュールには独自のグローバルがあり、ユニバーサルグローバルはありません。復元された機能をいくつかのモジュールに「移植」して、これを通常のモジュールのように使用できます。

- 保存する -

import marshal
def f(x):
    return x + 1
def g(x):
    return f(x) ** 2
funcfile = open("functions.pickle", "wb")
marshal.dump(f.func_code, funcfile)
marshal.dump(g.func_code, funcfile)
funcfile.close()

- 戻す -

import marshal
import types
open('sandbox.py', 'w').write('')  # create an empty module 'sandbox'
import sandbox
with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        func = types.FunctionType(code, sandbox.__dict__, code.co_name)
        setattr(sandbox, code.co_name, func)   # or sandbox.f = ... if the name is fixed
assert sandbox.g(3) == 16   # f(3) ** 2
# it is possible import them from other modules
from sandbox import g

編集済み:
外部から「sys」などのモジュールを「sandbox」名前空間にインポートすることもできます。

sandbox.sys = __import__('sys')

または同じ:

exec 'import sys' in sandbox.__dict__
assert 'sys' in sandbox, 'Verify imported into sandbox'

元のコードは、ipythonインタラクティブではなく、pythonプログラムまたは通常のpythonインタラクティブで実行すると機能します!!!

Ipythonは、sys.modulesのモジュールのdictではない奇妙な名前空間を使用します。通常のPythonまたはメインプログラムはsys.modules['__main__'].__dict__globals()として使用します。どのモジュールでも使用できますがthat_module.__dict__、これも問題ありません。問題となるのはipythonインタラクティブのみです。

于 2012-04-07T00:06:23.620 に答える
3

をインポートし__main__、そのモジュールで使用可能なメソッドを使用することで、グローバルオブジェクトをより適切に処理できます。これは、Pythonでほとんどすべてをシリアル化するためにdillが行うことです。基本的に、dillがインタラクティブに定義された関数をシリアル化する場合、有効なモジュール__main__を作成するシリアル化側と逆シリアル化側の両方で名前マングリングを使用します。__main__

>>> import dill
>>> 
>>> def bar(x):
...   return foo(x) + x
... 
>>> def foo(x):
...   return x**2
... 
>>> bar(3)
12
>>> 
>>> _bar = dill.loads(dill.dumps(bar))
>>> _bar(3)
12

実際、dillはその型をpickleレジストリに登録するため、使用するブラックボックスコードがpickleあり、実際に編集できない場合は、dillをインポートするだけで、サードパーティのコードにモンキーパッチを適用しなくても魔法のように機能させることができます。

または、インタプリタセッション全体を「Pythonイメージ」として送信する場合は、dillもそれを実行できます。

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
dude@sakurai>$ python
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('foobar.pkl')
>>> _bar(3)
12

sshを介して別のコンピューターにイメージを簡単に送信し、pickleのバージョン互換性と、Pythonの変更やインストールに関する通常の警告がある限り、中断したところから開始できます。

于 2013-10-16T21:17:28.653 に答える
3

ディル(他のピクルスのバリエーション、クラウドピクルなどと一緒に)は、ピクルスにされている機能がピクルスと一緒にメインモジュールにあるときに機能するようです。別のモジュールから関数をピクルス化する場合、ピクルス解除が発生するときにそのモジュール名が存在する必要があります。この制限を回避する方法を見つけることができないようです。

于 2014-10-15T18:42:52.230 に答える