Gibt es eine einfache Möglichkeit, eine Python-Funktion auszuwählen (oder ihren Code auf andere Weise zu serialisieren)?


100

Ich versuche, eine Funktion über eine Netzwerkverbindung zu übertragen (mit Asyncore). Gibt es eine einfache Möglichkeit, eine Python-Funktion (eine, die zumindest in diesem Fall keine Nebenwirkungen hat) für eine solche Übertragung zu serialisieren?

Idealerweise hätte ich gerne ein paar ähnliche Funktionen:

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

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

Antworten:


119

Sie können den Funktionsbytecode serialisieren und dann auf dem Aufrufer rekonstruieren. Das Marshall- Modul kann zum Serialisieren von Codeobjekten verwendet werden, die dann wieder zu einer Funktion zusammengesetzt werden können. dh:

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

Dann im Remote-Prozess (nach der Übertragung von code_string):

import marshal, types

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

func(10)  # gives 100

Ein paar Einschränkungen:

  • Das Marshall-Format (ein beliebiger Python-Bytecode) ist möglicherweise nicht mit den wichtigsten Python-Versionen kompatibel.

  • Funktioniert nur für die cpython-Implementierung.

  • Wenn die Funktion auf globale Elemente (einschließlich importierter Module, anderer Funktionen usw.) verweist, die Sie abrufen müssen, müssen Sie diese ebenfalls serialisieren oder auf der Remote-Seite neu erstellen. In meinem Beispiel wird nur der globale Namespace des Remote-Prozesses angegeben.

  • Sie müssen wahrscheinlich etwas mehr tun, um komplexere Fälle wie Schließungen oder Generatorfunktionen zu unterstützen.


1
In Python 2.5 ist das "neue" Modul veraltet. 'new.function' sollte nach einem 'import types' durch 'types.FunctionType' ersetzt werden, glaube ich.
Eric O Lebigot

2
Vielen Dank. Genau das habe ich gesucht. Basierend auf einigen flüchtigen Tests funktioniert es wie es für Generatoren ist.
Michael Fairley

2
Wenn Sie die ersten Absätze des Marschallmoduls lesen, sehen Sie, dass es dringend empfohlen wird, stattdessen Gurke zu verwenden? Gleiches gilt für die Gurkenseite. docs.python.org/2/library/marshal.html
dgorissen

1
Ich versuche, das marshalModul anzuwenden, um ein Wörterbuch von Wörterbüchern zu serialisieren, das als initialisiert wurde defaultdict(lambda : defaultdict(int)). Aber es gibt den Fehler zurück ValueError: unmarshallable object. Hinweis: Ich verwende Python2.7. Irgendeine Idee? Danke
user17375

2
Auf Python 3.5.3, foo.func_codewirft AttributeError. Gibt es eine andere Möglichkeit, den Funktionscode abzurufen?
AlQuemist

41

Schauen Sie sich Dill an , das die Pickle-Bibliothek von Python erweitert, um eine größere Vielfalt von Typen zu unterstützen, einschließlich Funktionen:

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

Es werden auch Verweise auf Objekte beim Schließen der Funktion unterstützt:

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

2
dill macht auch einen ziemlich guten Job darin, den Quellcode von Funktionen und Lambdas zu erhalten und diese auf der Festplatte zu speichern, wenn Sie dies dem Beizen von Objekten vorziehen.
Mike McKerns

14

Ich müsste mich an die Standardbibliothek für dieses spezielle Projekt halten.
Michael Fairley

21
Das heißt aber nicht, dass Sie sich den Code von Pyro nicht ansehen können, um zu sehen, wie es gemacht wird :)
Aaron Digulla

4
@ AaronDigulla- true, aber es ist erwähnenswert, dass Sie vor dem Lesen einer einzelnen Zeile des veröffentlichten Codes eines anderen immer die Lizenz der Software überprüfen sollten. Das Lesen des Codes einer anderen Person und das Wiederverwenden der Ideen ohne Angabe der Quelle oder Einhaltung von Lizenz- / Kopierbeschränkungen kann in vielen Fällen als Plagiat und / oder Urheberrechtsverletzung angesehen werden.
mdscruggs

12

Der einfachste Weg ist wahrscheinlich inspect.getsource(object)(siehe das Inspect-Modul ), einen String mit dem Quellcode für eine Funktion oder eine Methode zurückzugeben.


Dies sieht gut aus, außer dass der Funktionsname explizit im Code definiert ist, was etwas problematisch ist. Ich könnte die erste Zeile des Codes entfernen, aber das kann durch 'def \ / n func ():' unterbrochen werden. Ich könnte den Namen der Funktion mit der Funktion selbst auswählen, aber ich hätte keine Garantie dafür, dass der Name nicht kollidiert, oder ich müsste die Funktion in einen Wrapper legen, was immer noch nicht die sauberste Lösung ist, aber es könnte tun müssen.
Michael Fairley

1
Beachten Sie, dass das Inspect-Modul tatsächlich nur die Funktion fragt, wo sie definiert wurde, und dann diese Zeilen aus der Quellcodedatei einliest - kaum ausgefeilt.
zu viel PHP

1
Sie können den Namen der Funktion anhand des Attributs .__ name__ ermitteln. Sie könnten einen regulären Ausdruck für ^ def \ s * {name} \ s * ersetzen (und ihm einen beliebigen Namen geben. Es ist nicht narrensicher, aber es wird für die meisten Dinge funktionieren.
zu viel PHP

6

Es hängt alles davon ab, ob Sie die Funktion zur Laufzeit generieren oder nicht:

Wenn Sie dies tun inspect.getsource(object), funktioniert dies nicht für dynamisch generierte Funktionen, da die Quelle des Objekts aus der .pyDatei abgerufen wird. Daher können nur Funktionen, die vor der Ausführung definiert wurden, als Quelle abgerufen werden.

Und wenn Ihre Funktionen ohnehin in Dateien abgelegt sind, können Sie dem Empfänger Zugriff darauf gewähren und nur Modul- und Funktionsnamen weitergeben.

Die einzige Lösung für dynamisch erstellte Funktionen, die ich mir vorstellen kann, besteht darin, die Funktion vor der Übertragung als Zeichenfolge zu konstruieren, die Quelle zu senden und dann eval()auf der Empfängerseite.

Bearbeiten: Die marshalLösung sieht auch ziemlich schlau aus, wusste nicht, dass Sie etwas anderes als eingebaute serialisieren können



2
code_string = '' '
def foo (x):
    return x * 2
def bar (x):
    return x ** 2
'' '

obj = pickle.dumps (code_string)

Jetzt

exec (pickle.loads (obj))

foo (1)
> 2
Balken (3)
> 9

2

Du kannst das:

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

Jetzt transmit(fn_generator())wird fn(x,y)anstelle eines Verweises auf den Modulnamen die eigentliche Definition von gesendet .

Mit demselben Trick können Sie Klassen über das Netzwerk senden.


1

Die für dieses Modul verwendeten Grundfunktionen decken Ihre Abfrage ab, und Sie erhalten die beste Komprimierung über das Kabel. Siehe den lehrreichen Quellcode:

y_serial.py module :: warehouse Python-Objekte mit SQLite

"Serialisierung + Persistenz :: Komprimieren und kommentieren Sie Python-Objekte in wenigen Codezeilen in SQLite und rufen Sie sie später chronologisch nach Schlüsselwörtern ohne SQL ab. Das nützlichste" Standard "-Modul für eine Datenbank zum Speichern schemaloser Daten."

http://yserial.sourceforge.net


1

Cloudpickle ist wahrscheinlich das, wonach Sie suchen. Cloudpickle wird wie folgt beschrieben:

Cloudpickle ist besonders nützlich für Cluster-Computing, bei dem Python-Code über das Netzwerk gesendet wird, um auf Remote-Hosts ausgeführt zu werden, möglicherweise in der Nähe der Daten.

Anwendungsbeispiel:

def add_one(n):
  return n + 1

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

0

Hier ist eine Hilfsklasse, mit der Sie Funktionen umbrechen können, um sie auswählbar zu machen. Die bereits erwähnten Vorsichtsmaßnahmen marshalgelten, es wird jedoch versucht, nach Möglichkeit Gurke zu verwenden. Es werden keine Anstrengungen unternommen, um Globale oder Verschlüsse während der Serialisierung zu erhalten.

    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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.