¿Existe una manera fácil de seleccionar una función de Python (o serializar su código)?

100

Estoy tratando de transferir una función a través de una conexión de red (usando asyncore). ¿Existe una manera fácil de serializar una función de Python (una que, al menos en este caso, no tendrá efectos secundarios) para una transferencia como esta?

Idealmente, me gustaría tener un par de funciones similares a estas:

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

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

Respuestas:

120

Puede serializar el código de bytes de la función y luego reconstruirlo en la persona que llama. El módulo marshal se puede utilizar para serializar objetos de código, que luego se pueden volver a ensamblar en una función. es decir:

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

Luego, en el proceso remoto (después de transferir code_string):

import marshal, types

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

func(10)  # gives 100

Algunas advertencias:

  • El formato de marshal (cualquier código de bytes de Python para el caso) puede no ser compatible entre las principales versiones de Python.

  • Solo funcionará para la implementación de cpython.

  • Si la función hace referencia a globales (incluidos módulos importados, otras funciones, etc.) que necesita recoger, deberá serializarlos también o recrearlos en el lado remoto. Mi ejemplo simplemente le da el espacio de nombres global del proceso remoto.

  • Probablemente deba hacer un poco más para admitir casos más complejos, como cierres o funciones de generador.

Brian
fuente
1
En Python 2.5, el módulo "nuevo" está obsoleto. 'new.function' debería ser reemplazado por 'types.FunctionType', después de un "tipos de importación", creo.
Eric O Lebigot
2
Gracias. Esto es exactamente lo que estaba buscando. Según algunas pruebas superficiales, funciona como está para los generadores.
Michael Fairley
2
Si lee los primeros dos párrafos del módulo marshal, ¿ve que sugiere usar pickle en su lugar? Lo mismo para la página de pepinillos. docs.python.org/2/library/marshal.html
dgorissen
1
Estoy tratando de aplicar el marshalmódulo para serializar un diccionario de diccionarios inicializados como defaultdict(lambda : defaultdict(int)). Pero devuelve el error ValueError: unmarshallable object. Tenga en cuenta que estoy usando python2.7. ¿Alguna idea? Gracias
user17375
2
En Python 3.5.3, foo.func_codeaumenta AttributeError. ¿Existe otra forma de obtener el código de función?
AlQuemist
41

Consulte Dill , que amplía la biblioteca pickle de Python para admitir una mayor variedad de tipos, incluidas funciones:

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

También admite referencias a objetos en el cierre de la función:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3
Josh Rosen
fuente
2
eneldo también hace un buen trabajo al obtener el código fuente de las funciones y lambdas y guardarlos en el disco, si lo prefiere al decapado de objetos.
Mike McKerns
14

Pyro puede hacer esto por usted .

RichieHindle
fuente
Necesitaría seguir con la biblioteca estándar para este proyecto en particular.
Michael Fairley
21
Pero eso no significa que no puedas mirar el código de Pyro para ver cómo se hace :)
Aaron Digulla
4
@ AaronDigulla: cierto, pero vale la pena mencionar que antes de leer una sola línea del código publicado de otra persona, siempre debe verificar la licencia del software. Leer el código de otra persona y reutilizar las ideas sin citar la fuente o adherirse a las restricciones de licencia / copia podría considerarse plagio y / o violación de derechos de autor en muchos casos.
mdscruggs
12

La forma más sencilla es probablemente inspect.getsource(object)(ver el módulo de inspección ) que devuelve una cadena con el código fuente de una función o método.

Aaron Digulla
fuente
Esto se ve bien, excepto que el nombre de la función está definido explícitamente en el código, lo cual es un poco problemático. Podría quitar la primera línea del código, pero eso se puede romper haciendo algo como 'def \ / n func ():'. Podría elegir el nombre de la función con la función en sí, pero no tendría garantías de que el nombre no colisionara, o tendría que poner la función en un contenedor, que todavía no es la solución más limpia, pero podría tener que hacerlo.
Michael Fairley
1
Tenga en cuenta que el módulo de inspección en realidad solo pregunta a la función dónde se definió y luego lee esas líneas del archivo de código fuente, algo poco sofisticado.
demasiado php
1
Puede averiguar el nombre de la función usando su atributo .__ name__. Podría hacer un reemplazo de expresiones regulares en ^ def \ s * {name} \ s * (y darle el nombre que desee. No es infalible, pero funcionará para la mayoría de las cosas.
demasiado php
6

Todo depende de si genera la función en tiempo de ejecución o no:

Si lo hace inspect.getsource(object), no funcionará para funciones generadas dinámicamente, ya que obtiene la fuente del objeto del .pyarchivo, por lo que solo las funciones definidas antes de la ejecución se pueden recuperar como fuente.

Y si sus funciones se colocan en archivos de todos modos, ¿por qué no dar acceso al receptor a ellas y solo pasar los nombres de los módulos y las funciones?

La única solución para las funciones creadas dinámicamente que se me ocurre es construir la función como una cadena antes de la transmisión, la fuente de transmisión y luego eval() en el lado del receptor.

Editar: la marshalsolución también parece bastante inteligente, no sabía que podía serializar algo más que los incorporados

Kurczak
fuente
2
cadena_código = '' '
def foo (x):
    devolver x * 2
def bar (x):
    volver x ** 2
'' '

obj = pickle.dumps (cadena_código)

Ahora

exec (pickle.loads (obj))

foo (1)
> 2
barra (3)
> 9
Yanni Papadakis
fuente
2

Puedes hacerlo:

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

Ahora, transmit(fn_generator())enviará la definición real de en fn(x,y)lugar de una referencia al nombre del módulo.

Puede utilizar el mismo truco para enviar clases a través de la red.

Fardin
fuente
1

Las funciones básicas utilizadas para este módulo cubren su consulta, además de que obtiene la mejor compresión por cable; ver el código fuente instructivo:

Módulo y_serial.py :: almacén de objetos Python con SQLite

"Serialización + persistencia :: en unas pocas líneas de código, comprima y anote objetos Python en SQLite; luego recupérelos cronológicamente por palabras clave sin SQL. El módulo" estándar "más útil para una base de datos para almacenar datos sin esquema".

http://yserial.sourceforge.net


fuente
1

Cloudpickle es probablemente lo que estás buscando. Cloudpickle se describe de la siguiente manera:

cloudpickle es especialmente útil para la computación en clúster donde el código Python se envía a través de la red para ejecutarse en hosts remotos, posiblemente cerca de los datos.

Ejemplo de uso:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)
Simón C
fuente
0

Aquí hay una clase de ayuda que puede usar para ajustar funciones a fin de hacerlas seleccionables. Se marshalaplicarán las advertencias ya mencionadas , pero se hace un esfuerzo para usar pickle siempre que sea posible. No se hace ningún esfuerzo para preservar globales o cierres a través de la serialización.

    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)
memeplex
fuente