2012-04-06 6 views
11

Im Nachgang zu dieser Frage: Is there an easy way to pickle a python function (or otherwise serialize its code)?Wie pickel ich eine Python-Funktion mit ihren Abhängigkeiten?

Ich möchte ein Beispiel für diese Kugel aus dem obigen Beitrag sehen:

„Wenn die Funktion Referenzen Globals (einschließlich importierter Module, anderen Funktionen usw.) Sie müssen diese ebenfalls serialisieren oder auf der Remote-Seite neu erstellen. In meinem Beispiel wird nur der globale Namespace des Remote-Prozesses angegeben. "

Ich habe einen einfachen Test zu gehen, wo ich in eine Datei Marshal mit einem Funktionen-Byte-Code schreibe:

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) 

Dann eine neue Python-Instanz starten ich tun:

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

Dies führt zu a:

NameError: global name 'g' is not defined 

Dies ist unabhängig von den verschiedenen Ansätze, die ich gemacht habe, einschließlich g. Ich habe im Prinzip den gleichen Ansatz versucht, g als f zu senden, aber f kann g immer noch nicht sehen. Wie bekomme ich g in den globalen Namespace, damit er im Empfangsprozess von f verwendet werden kann?

Jemand empfiehlt auch Pyro als ein Beispiel dafür, wie dies zu tun ist. Ich habe bereits versucht, den Code im Discoprojekt zu verstehen. Ich nahm ihre dPickle-Klasse und versuchte, ihre disco/tests/test_pickle.py-Funktionalität in einer eigenständigen App ohne Erfolg neu zu erstellen. Mein Test hatte Probleme beim Marshallen mit dem Dump-Aufruf. Wie auch immer, vielleicht ist eine Pyro Exploration die nächste.

Zusammenfassend ist die Grundfunktionalität, die ich habe, in der Lage, eine Methode über die Leitung zu senden und alle grundlegenden "Arbeitsbereich" Methoden mit sich zu senden (wie g).

Beispiel mit Änderungen von Antwort:

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

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

Antwort

3

ich versucht habe, im Grunde den gleichen Ansatz g über wie f Senden aber f kann g immer noch nicht sehen. Wie bekomme ich g in den globalen Namespace, damit er im Empfangsprozess von f verwendet werden kann?

Weisen Sie sie dem globalen Namen g zu. (Ich sehe, dass Sie f zu func2 statt f zuweisen. Wenn Sie so etwas mit g tun, dann ist es klar, warum fg nicht finden kann. Denken Sie daran, Namensauflösung geschieht zur Laufzeit - g wird nicht angesehen bis Sie f anrufen.)

Natürlich schätze ich, da Sie den Code nicht gezeigt haben, den Sie verwenden, um dies zu tun.

Es kann am besten sein, ein separates Wörterbuch für den globalen Namespace für die Funktionen zu erstellen, die Sie entpacken - eine Sandbox. Auf diese Weise werden alle ihre globalen Variablen von dem Modul getrennt, in dem Sie dies tun.So könnten Sie so etwas tun:

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) 

In diesem Beispiel nehme ich an, dass Sie die Code-Objekte aus allen Funktionen in einer Datei gesetzt haben, eine nach dem anderen, und wenn sie bei der Lektüre, erhalte ich die Der Name des Codeobjekts wird als Basis für den Namen des Funktionsobjekts und den Namen, unter dem er im Sandbox-Wörterbuch gespeichert ist, verwendet.

Innerhalb der ungebeizten Funktionen, der Sandbox-Wörterbuch ist ihre globals() und so innerhalb f(), g bekommt sein Wert von sandbox["g"]. Um f dann zu benennen wäre: sandbox["f"]("blah")

+0

Oh wow, ich wusste nicht, dass die zugewiesene Referenz einen Unterschied gemacht hat! Vielen Dank! Wird Arbeitscode posten. –

+1

@RyanR. : Ich habe selbst einen Code veröffentlicht. – kindall

+0

Großartig, ich mag die Sandbox. Sie möchten das nächste automatische Serialisieren aller Funktionenabhängigkeiten automatisch erkunden. So ähnlich wie die disco modutil.find_modules Methode. Schätze die Hilfe. –

2

Jedes Modul hat seine eigenen Globals, es gibt keine universellen Globals. Wir können wiederhergestellte Funktionen in ein Modul "implantieren" und dieses wie ein normales Modul verwenden. sparen - -

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

- wiederherstellen -

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 

Editiert:
Sie können tun, importieren auch einige Modul .e.g. "Sys" auf "Sandbox" Namensraum von außen:

sandbox.sys = __import__('sys') 

oder die gleiche:

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

Ihr Original-Code würde funktionieren, wenn Sie es nicht in ipython interaktiv, sondern in einem Python-Programm tun oder normales Python interaktiv !!!

Ipython verwendet einen seltsamen Namespace, der kein dict eines Moduls von sys.modules ist. Normales Python oder irgendein Hauptprogramm benutzt sys.modules['__main__'].__dict__ als globals(). Jedes Modul verwendet that_module.__dict__, was auch OK ist, nur ipython interactive ist ein Problem.

+0

Danke! +1 War auch neugierig. –

+1

@RyanR. Ihr ursprünglicher Code funktioniert, wenn normales Python nicht ipython verwendet wird. – hynekcer

+0

Ist nicht importieren x; x.method() 'Typ verwendet ein Problem in den Remote-Skripten? Wie in: http://stackoverflow.com/questions/10099326/how-to-do-an-embedded-python-module-for-remote-sandbox-execution –

16

Die Wolke Paket tut dies - nur 'Cloud installieren pip' und dann:

import cloud, pickle 
def foo(x): 
    return x*3 
def bar(z): 
    return foo(z)+1 
x = cloud.serialization.cloudpickle.dumps(bar) 
del foo 
del bar 
f = pickle.loads(x) 
print f(3) # displays "10" 

Mit anderen Worten, rufen Sie einfach cloudpickle.dump() oder cloudpickle.dumps() die gleiche Art und Weise würden Sie Verwenden Sie pickle. *, und verwenden Sie später die native pickle.load() oder pickle.loads() zum Auftauen.

Picloud hat das 'cloud' python-Paket unter der LGPL veröffentlicht, und andere Open-Source-Projekte verwenden es bereits (googeln Sie nach "cloudpickle.py", um ein paar zu sehen). Die Dokumentation auf picloud.com gibt Ihnen einen Eindruck davon, wie leistungsfähig dieser Code ist und warum er einen Anreiz hatte, sich für allgemeine Code-Beizarbeiten zu engagieren - sein ganzes Geschäft baut darauf auf. Die Idee ist, dass, wenn Sie cpu_intensive_function haben() und wollen es auf Amazon EC2 Gitter laufen, einfach ersetzen:

cpu_intensive_function(some, args) 

mit:

cloud.call(cpu_intensive_function, some, args) 

Letzterer verwendet cloudpickle alle abhängigen Code beizen up und Daten, versendet es an EC2, führt es aus und gibt die Ergebnisse an Sie zurück, wenn Sie cloud.result() aufrufen.(Picloud-Rechnungen in Millisekunden-Schritten, es ist billig, und ich benutze es die ganze Zeit für Monte-Carlo-Simulationen und finanzielle Zeitreihenanalyse, wenn ich Hunderte von CPU-Kernen für nur ein paar Sekunden brauche. Ich kann nicht genug gut sagen Dinge darüber und ich arbeite nicht einmal dort.)

+0

danke Sir :) Ich habe mit Dill gekämpft für ein paar Stunden, aber Cloud funktioniert einfach gerade Ich glaube, dass dies die akzeptierte Antwort sein sollte – NiCU

+2

Da das ursprüngliche PiCloud Client SDK nicht mehr gepflegt wird, wurde ein neues Projekt gestartet, nur um die Cloudpickle-Funktionen zu erhalten: http: // github.com/cloudpipe/cloudpickle: 'pip install cloudpickle' – ogrisel

+1

@stevegt: Ihr Beispiel scheint nicht eingebaut Funktion korrekt zu funktionieren :) – user2284570

3

Sie können eine bessere Handhabung für globale Objekte erhalten, indem Sie __main__ importieren und die in diesem Modul verfügbaren Methoden verwenden. Dies ist, was dill tut, um fast alles in Python zu serialisieren. Wenn Dill seriell eine interaktiv definierte Funktion serialisiert, verwendet er einige Namensänderungen unter __main__ sowohl auf der Serialisierungs- als auch auf der Deserialisierungsseite, die __main__ zu einem gültigen Modul machen.

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

Eigentlich Dill registriert es Typen in den pickle Registry ist, also, wenn Sie etwas Blackbox Code haben, der pickle verwendet, und Sie können es nicht wirklich bearbeiten, dann nur den Import Dill kann es auf magische Weise ohne funktioniert monkeypatching die 3rd Party Code.

Oder, wenn Sie möchten, dass die gesamte Interpretersitzung als "Python-Bild" gesendet wird, kann dill das auch tun.

>>> # continuing from above 
>>> dill.dump_session('foobar.pkl') 
>>> 
>>> ^D 
[email protected]>$ 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 

Sie können ganz einfach das Bild über ssh auf einem anderen Computer senden, und beginnen, wo man so lange weg dort links als Versionskompatibilität von Beize gibt es und die üblichen Warnungen über Python zu ändern und Dinge installiert.

+0

aber dann, wenn ein Python-Programm foo und bar und Pickles bar in eine Datei (mit dill), und ein anderes Python-Programm lädt die gebeizte Datei in _bar und ruft _bar (3) auf, es ist fehlerhaft, wobei foo undefiniert ist. Warum funktioniert es in diesem Fall nicht? –

+0

Ich bin mir nicht sicher, ob ich sehe, was genau du fragst, kannst du vielleicht mehr Details liefern (entweder in einer eigenen Frage oder auf der Github-Issues-Seite für 'Dill')? –

+0

Ich öffnete eine neue Ausgabe hier: https://github.com/uqfoundation/dill/issues/176 –

0

Dill (zusammen mit anderen Pickle-Varianten, Cloudpickle usw.) scheint zu funktionieren, wenn die zu entkalkenden Funktionen zusammen mit dem Beizen im Hauptmodul sind. Wenn Sie eine Funktion von einem anderen Modul aus markieren, muss dieser Modulname vorhanden sein, wenn das Entpacken stattfindet. Ich finde keinen Ausweg aus dieser Einschränkung.

+0

Sie können. Siehe: http://stackoverflow.com/questions/26389981/serialize-a-python-function-with-dependencies –

Verwandte Themen