write funcion dumps cpickle python serialization closures

funcion - Python serializar cierres léxicos?



pickle python documentation (5)

¿Hay alguna manera de serializar un cierre léxico en Python usando la biblioteca estándar? pickle y mariscal parecen no funcionar con cierres léxicos. Realmente no me importan los detalles de la serialización binaria frente a la cadena, etc., simplemente tiene que funcionar. Por ejemplo:

def foo(bar, baz) : def closure(waldo) : return baz * waldo return closure

Me gustaría simplemente poder descargar instancias de cierre de un archivo y leerlas de nuevo.

Editar: Una manera relativamente obvia de que esto podría resolverse es con algunos hacks de reflexión para convertir cierres léxicos en objetos de clase y viceversa. Uno podría entonces convertir a clases, serializar, deserializar, convertir de nuevo a cierres. Diablos, dado que Python es tipeado en pato, si sobrecargó el operador de llamada de función de la clase para que pareciera una función, ni siquiera necesitaría convertirlo a un cierre y el código que lo utiliza no sabría la diferencia. Si hay algún gurú de API de reflexión de Python disponible, por favor hable.


¡Sí! Lo obtuve (al menos creo), es decir, el problema más genérico de decapado de una función. Python es tan maravilloso :), descubrí la mayor parte a través de la función dir () y un par de búsquedas en la web. También es maravilloso tenerlo [afortunadamente] resuelto, lo necesitaba también.

No he probado mucho sobre cuán robusto es este código co_code (fcns anidados, etc.), y sería bueno si alguien pudiera buscar cómo enganchar Python para que las funciones puedan escanearse automáticamente (por ejemplo, a veces pueden ser args de cierre).

Módulo de Cython _pickle_fcn.pyx

# -*- coding: utf-8 -*- cdef extern from "Python.h": object PyCell_New(object value) def recreate_cell(value): return PyCell_New(value)

Archivo Python

#!/usr/bin/env python # -*- coding: utf-8 -*- # author gatoatigrado [ntung.com] import cPickle, marshal, types import pyximport; pyximport.install() import _pickle_fcn def foo(bar, baz) : def closure(waldo) : return baz * waldo return closure # really this problem is more about pickling arbitrary functions # thanks so much to the original question poster for mentioning marshal # I probably wouldn''t have found out how to serialize func_code without it. fcn_instance = foo("unused?", -1) code_str = marshal.dumps(fcn_instance.func_code) name = fcn_instance.func_name defaults = fcn_instance.func_defaults closure_values = [v.cell_contents for v in fcn_instance.func_closure] serialized = cPickle.dumps((code_str, name, defaults, closure_values), protocol=cPickle.HIGHEST_PROTOCOL) code_str_, name_, defaults_, closure_values_ = cPickle.loads(serialized) code_ = marshal.loads(code_str_) closure_ = tuple([_pickle_fcn.recreate_cell(v) for v in closure_values_]) # reconstructing the globals is like pickling everything :) # for most functions, it''s likely not necessary # it probably wouldn''t be too much work to detect if fcn_instance global element is of type # module, and handle that in some custom way # (have the reconstruction reinstantiate the module) reconstructed = types.FunctionType(code_, globals(), name_, defaults_, closure_) print(reconstructed(3))

aclamaciones,
Nicholas

EDITAR : se necesita un manejo global más sólido para los casos del mundo real. fcn.func_code.co_names enumera los nombres globales.


#!python import marshal, pickle, new def dump_func(f): if f.func_closure: closure = tuple(c.cell_contents for c in f.func_closure) else: closure = None return marshal.dumps(f.func_code), f.func_defaults, closure def load_func(code, defaults, closure, globs): if closure is not None: closure = reconstruct_closure(closure) code = marshal.loads(code) return new.function(code, globs, code.co_name, defaults, closure) def reconstruct_closure(values): ns = range(len(values)) src = ["def f(arg):"] src += [" _%d = arg[%d]" % (n, n) for n in ns] src += [" return lambda:(%s)" % '',''.join("_%d"%n for n in ns), ''''] src = ''/n''.join(src) try: exec src except: raise SyntaxError(src) return f(values).func_closure if __name__ == ''__main__'': def get_closure(x): def the_closure(a, b=1): return a * x + b, some_global return the_closure f = get_closure(10) code, defaults, closure = dump_func(f) dump = pickle.dumps((code, defaults, closure)) code, defaults, closure = pickle.loads(dump) f = load_func(code, defaults, closure, globals()) some_global = ''some global'' print f(2)


PiCloud ha lanzado un pickler de fuente abierta (LGPL) que puede manejar el cierre de funciones y mucho más cosas útiles. Se puede usar independientemente de su infraestructura de computación en la nube; es solo un recolector normal. Todo el shebang está documentado aquí , y puedes descargar el código a través de ''pip install cloud''. De todos modos, hace lo que quieres. Demostremos que al decapar un cierre:

import pickle from StringIO import StringIO import cloud # generate a closure def foo(bar, baz): def closure(waldo): return baz * waldo return closure closey = foo(3, 5) # use the picloud pickler to pickle to a string f = StringIO() pickler = cloud.serialization.cloudpickle.CloudPickler(f) pickler.dump(closey) #rewind the virtual file and reload f.seek(0) closey2 = pickle.load(f)

Ahora tenemos closey , el cierre original y closey2 , el que se ha restaurado a partir de una serialización de cadenas. Vamos a probarlos.

>>> closey(4) 20 >>> closey2(4) 20

Hermosa. El módulo es puro pitón; puedes abrirlo y ver fácilmente qué hace funcionar a la magia. (La respuesta es mucho código).


Si, para empezar, simplemente utiliza una clase con un método __call__ , todo debería funcionar sin problemas con pickle .

class foo(object): def __init__(self, bar, baz): self.baz = baz def __call__(self,waldo): return self.baz * waldo

Por otro lado, un truco que convierta un cierre en una instancia de una nueva clase creada en tiempo de ejecución no funcionaría, debido a la forma en que pickle trata las clases y las instancias. pickle no almacena clases; solo un nombre de módulo y nombre de clase. Al leer una instancia o clase, intenta importar el módulo y encontrar la clase requerida en él. Si usó una clase creada sobre la marcha, no tiene suerte.


Receta 500261: Tuples con nombre contiene una función que define una clase sobre la marcha. Y esta clase apoya el decapado.

Aquí está la esencia:

result.__module__ = _sys._getframe(1).f_globals.get(''__name__'', ''__main__'')

Combinado con la sugerencia de @Greg Ball para crear una nueva clase en tiempo de ejecución, podría responder a su pregunta.