Cómo encurtir una cadena de devolución de llamada
Pregunta
Tengo una máquina de estado definida por el usuario en Twisted. El usuario puede definir los manejadores para diferentes cambios de estado, que implemento utilizando un retorcido diferido a los que les dejo agregar devoluciones de llamada. Cada vez que me muevo de un estado a otro, simplemente disparo el apropiado apropiado.
Uno de los requisitos del proyecto es la capacidad de guardar esta máquina de estado en el disco, junto con todas sus devoluciones de llamada. Pensé que podría simplemente encurtir la máquina de estado y terminaría, pero obtengo un pickleerror cuando trato de serializar las funciones definidas por el usuario.
¿Alguien sabe de una forma de serializar funciones? El error se reproduce en la siguiente muestra del código:
import pickle
from twisted.internet.utils import defer
def foo(*args):
def bar():
print args
return bar
d = defer.Deferred()
d.addCallback(foo("Hello", "world"))
pickle.dumps(d)
Esta última línea da el siguiente error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.5/pickle.py", line 1366, in dumps
Pickler(file, protocol).dump(obj)
File "/usr/lib/python2.5/pickle.py", line 224, in dump
self.save(obj)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 725, in save_inst
save(stuff)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 649, in save_dict
self._batch_setitems(obj.iteritems())
File "/usr/lib/python2.5/pickle.py", line 663, in _batch_setitems
save(v)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 600, in save_list
self._batch_appends(iter(obj))
File "/usr/lib/python2.5/pickle.py", line 615, in _batch_appends
save(x)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 562, in save_tuple
save(element)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 562, in save_tuple
save(element)
File "/usr/lib/python2.5/pickle.py", line 286, in save
f(self, obj) # Call unbound method with explicit self
File "/usr/lib/python2.5/pickle.py", line 748, in save_global
(obj, module, name))
pickle.PicklingError: Can't pickle <function bar at 0xb753fe2c>: it's not found as __main__.bar
¿Hay alguna solución para esto? ¿Quizás necesito restringir los tipos de funciones que los usuarios pueden agregar como devoluciones de llamada?
Gracias,
Jonathan
Solución
No intentes encurtir diferidos. No es compatible con Twisted. Incluso si logras construir algo que parece funcionar (y no es enteramente imposible), una versión posterior de Twisted podría romper todo su estado guardado.
Los diferidos son para controlar el flujo de eventos a través de su código. No son para almacenar el estado de aplicación. Si desea persistir en su estado de solicitud, separarlo de cualquier aplazamiento y serializar sólo eso.
Cuando hace esto, probablemente también desee evitar usar Pickle para el formato de serialización. Pickle no es una buena forma de almacenar datos. Es un formato de alto complejo que es muy sensible a los cambios en las versiones de Python y las versiones de la biblioteca. No tiene medios para definir un esquema, por lo que nunca puede estar realmente seguro de lo que está serializando o lo que ha serializado. Es muy difícil inspeccionar un encurtido por separado de cargarlo, por lo que si alguna vez se rompe (como lo hará si decide cambiar el nombre de una clase de la que tiene instancias en escabeche), recuperar los datos es una molestia importante.
Otros consejos
Reemplace las funciones de Foo/Bar con una instancia de clase llamable:
class foo(object):
def __init__(self, *args):
self.args = args
def __call__(self):
print self.args
d = defer.Deferred()
d.addCallback(foo("Hello", "world"))
pickle.dumps(d)