2011-01-28 2 views
3

У меня есть пользовательский конечный автомат в Twisted. Пользователь может определить обработчики для разных изменений состояния, которые я реализую, используя Twisted отложенное, что я позволяю им добавлять обратные вызовы. Всякий раз, когда я перехожу из одного государства в другое, я просто увольняю соответствующее отсроченное.Как рассортировать цепочку обратного вызова

Одним из требований проекта является возможность сохранения этого состояния на диск вместе со всеми его обратными вызовами. Я думал, что могу просто рассортировать конечный автомат, и все будет готово, но я получаю PickleError, когда пытаюсь сериализовать пользовательские функции.

Кто-нибудь знает способ сериализации функций? Ошибка воспроизводится в приведенном ниже примере кода:

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) 

Последняя строка дает следующее сообщение об ошибке:

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 

Существуют ли какие-либо решения этого? Может быть, мне нужно ограничить типы функций, которые пользователи могут добавлять в качестве обратных вызовов?

Спасибо,
Джонатан

+0

Я думаю, здесь есть два отдельных вопроса: (1) как хранить цепочку обратного вызова - хранить их отдельно, как предложил Жан-Поль, и (2) как хранить обратные вызовы сами по себе - использовать функторы, например, vz0 предлагает. Как я могу принять оба ваших ответа? – Jonathan

ответ

2

Не пытайтесь мариновать Deferreds. Это не поддерживается Twisted. Даже если вам удастся создать что-то, что работает (и это не полностью невозможно), более поздняя версия Twisted может сломать все сохраненное состояние.

Отложенные средства предназначены для управления потоком событий через ваш код. Они не предназначены для хранения состояния приложения. Если вы хотите сохранить состояние своего приложения, отделите его от любых Отложенных и сериализуйте только.

Когда вы это сделаете, вы, вероятно, также захотите избежать использования pickle для формата сериализации. Рассол не является хорошим способом хранения данных. Это очень сложный формат, который очень чувствителен к изменениям в версиях Python и версиях библиотек. У него нет средств для определения схемы, поэтому вы никогда не можете быть уверены в том, что вы сериализуете или что вы сериализуете. Очень сложно осмотреть рассол отдельно от его загрузки, поэтому, если он когда-либо сломается (как если бы вы решили переименовать класс, который у вас есть маринованные экземпляры), восстановление данных является серьезной проблемой.

0

Заменить Foo/бар функции с экземпляром класса отзывной:

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) 
+0

Предположительно, идея травления функции заключается в том, что вы можете закрыть свою программу или перенести маринованный объект и ожидать, что функция будет вызываться в другом месте. Это не будет работать, потому что адреса функций определяются во время выполнения. Он может работать в течение нескольких последовательных прогонов программы, но это небезопасно. –

+0

Взял мой провиант, потому что я думаю, что понял, что вы говорите о функторе. +1, теперь, хотя этот тип обратного вызова может вызвать проблемы с дизайном, если вы пытаетесь быть очень ООП и типов. :) –

+0

@San Jacinto: Да, это работает. В документации на рассол четко указано, что можно и нельзя травить. http://docs.python.org/library/pickle.html#what-can-be-pickled-and-unpickled – vz0

Смежные вопросы