C'è un modo semplice per decapare una funzione python (o altrimenti serializzare il suo codice)?


100

Sto provando a trasferire una funzione attraverso una connessione di rete (usando asyncore). C'è un modo semplice per serializzare una funzione python (una che, almeno in questo caso, non avrà effetti collaterali) per un trasferimento come questo?

Idealmente mi piacerebbe avere un paio di funzioni simili a queste:

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

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

Risposte:


120

È possibile serializzare il bytecode della funzione e quindi ricostruirlo sul chiamante. Il modulo marshal può essere utilizzato per serializzare oggetti di codice, che possono quindi essere riassemblati in una funzione. cioè:

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

Quindi nel processo remoto (dopo aver trasferito code_string):

import marshal, types

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

func(10)  # gives 100

Alcuni avvertimenti:

  • Il formato di marshal (qualsiasi bytecode python per quella materia) potrebbe non essere compatibile tra le principali versioni di python.

  • Funzionerà solo per l'implementazione di cpython.

  • Se la funzione fa riferimento a variabili globali (inclusi moduli importati, altre funzioni, ecc.) Che è necessario raccogliere, sarà necessario serializzare anche queste o ricrearle sul lato remoto. Il mio esempio fornisce solo lo spazio dei nomi globale del processo remoto.

  • Probabilmente dovrai fare un po 'di più per supportare casi più complessi, come chiusure o funzioni del generatore.


1
In Python 2.5, il "nuovo" modulo è deprecato. "new.function" dovrebbe essere sostituito da "types.FunctionType", dopo un "import types", credo.
Eric O Lebigot

2
Grazie. Questo e 'esattamente quello che stavo cercando. Sulla base di alcuni test superficiali, funziona come per i generatori.
Michael Fairley il

2
Se leggi il primo paio di paragrafi sul modulo marshal, vedi che suggerisce fortemente di usare pickle invece? Lo stesso per la pagina dei sottaceti. docs.python.org/2/library/marshal.html
dgorissen

1
Sto cercando di applicare il marshalmodulo per serializzare un dizionario di dizionari inizializzato come defaultdict(lambda : defaultdict(int)). Ma restituisce l'errore ValueError: unmarshallable object. Nota sto usando python2.7. Qualche idea? Grazie
user17375

2
In Python 3.5.3, foo.func_codesolleva AttributeError. C'è un altro modo per ottenere il codice della funzione?
AlQuemist

41

Dai un'occhiata a Dill , che estende la libreria pickle di Python per supportare una maggiore varietà di tipi, comprese le funzioni:

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

Supporta anche i riferimenti agli oggetti nella chiusura della funzione:

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

2
dill fa anche un ottimo lavoro nell'ottenere il codice sorgente da funzioni e lambda e salvarli su disco, se lo preferisci al decapaggio degli oggetti.
Mike McKerns

14

Pyro è in grado di farlo per te .


Avrei bisogno di restare con la libreria standard per questo particolare progetto.
Michael Fairley

21
Ma questo non significa che non puoi guardare il codice di Pyro per vedere come è fatto :)
Aaron Digulla

4
@ AaronDigulla- vero, ma vale la pena ricordare che prima di leggere una singola riga del codice pubblicato di qualcun altro, dovresti sempre controllare la licenza del software. Leggere il codice di qualcun altro e riutilizzare le idee senza citare la fonte o aderire a vincoli di licenza / copia potrebbe essere considerato plagio e / o violazione del copyright in molti casi.
mdscruggs

12

Il modo più semplice è probabilmente inspect.getsource(object)(vedere il modulo inspect ) che restituisce una stringa con il codice sorgente per una funzione o un metodo.


Questo sembra buono, tranne che il nome della funzione è definito esplicitamente nel codice, il che è leggermente problematico. Potrei eliminare la prima riga del codice, ma è divisibile facendo qualcosa come "def \ / n func ():". Potrei mettere il nome della funzione in sottofondo con la funzione stessa, ma non avrei garanzie che il nome non si scontrerebbe, o dovrei mettere la funzione in un wrapper, che non è ancora la soluzione più pulita, ma potrebbe essere necessario.
Michael Fairley il

1
Si noti che il modulo inspect in realtà sta solo chiedendo alla funzione dove è stata definita, e poi sta leggendo quelle righe dal file del codice sorgente - difficilmente sofisticato.
troppo php il

1
Puoi trovare il nome della funzione usando il suo attributo .__ name__. Potresti sostituire una regex su ^ def \ s * {name} \ s * (e dagli il nome che preferisci. Non è infallibile, ma funzionerà per la maggior parte delle cose.
troppo php

6

Tutto dipende dal fatto che si generi la funzione in fase di esecuzione o meno:

Se lo fai inspect.getsource(object), non funzionerà per le funzioni generate dinamicamente poiché ottiene l'origine dell'oggetto dal .pyfile, quindi solo le funzioni definite prima dell'esecuzione possono essere recuperate come origine.

E se le tue funzioni sono comunque inserite nei file, perché non dare al ricevitore l'accesso ad esse e passare solo i nomi dei moduli e delle funzioni.

L'unica soluzione per le funzioni create dinamicamente a cui posso pensare è costruire la funzione come una stringa prima della trasmissione, la sorgente di trasmissione e poi eval()dal lato del ricevitore.

Modifica: la marshalsoluzione sembra anche piuttosto intelligente, non sapevo che puoi serializzare qualcos'altro che non sia integrato



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

obj = pickle.dumps (code_string)

Adesso

exec (pickle.loads (obj))

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

2

Puoi farlo:

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

Ora transmit(fn_generator())invierà la definizione effettiva fn(x,y)invece di un riferimento al nome del modulo.

Puoi usare lo stesso trucco per inviare classi attraverso la rete.


1

Le funzioni di base utilizzate per questo modulo coprono la tua query, inoltre ottieni la migliore compressione in rete; guarda il codice sorgente istruttivo:

y_serial.py module :: warehouse oggetti Python con SQLite

"Serializzazione + persistenza :: in poche righe di codice, comprime e annota oggetti Python in SQLite; quindi recuperali cronologicamente tramite parole chiave senza SQL. Modulo" standard "più utile per un database per memorizzare dati senza schema."

http://yserial.sourceforge.net


1

Cloudpickle è probabilmente quello che stai cercando. Cloudpickle è descritto come segue:

cloudpickle è particolarmente utile per il cluster computing in cui il codice Python viene spedito sulla rete per essere eseguito su host remoti, possibilmente vicini ai dati.

Esempio di utilizzo:

def add_one(n):
  return n + 1

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

0

Ecco una classe helper che puoi usare per racchiudere le funzioni in modo da renderle selezionabili. marshalSi applicano gli avvertimenti già menzionati per , ma viene fatto uno sforzo per utilizzare il sottaceto quando possibile. Non viene fatto alcuno sforzo per preservare le globali o le chiusure attraverso la serializzazione.

    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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.