Python сериализует лексические закрытия?
Есть ли способ сериализации лексического закрытия в Python с использованием стандартной библиотеки? рассол и маршал, похоже, не работают с лексическими закрытиями. Меня не волнуют детали двоичной и сериализации строк и т.д., Она просто должна работать. Например:
def foo(bar, baz) :
def closure(waldo) :
return baz * waldo
return closure
Я бы хотел просто удалить экземпляры закрытия в файл и прочитать их обратно.
Изменить:
Один относительно очевидный способ, которым это можно решить, - это некоторые рефлексивные хаки для преобразования лексических замыканий в объекты класса и наоборот. Затем можно было бы преобразовать в классы, сериализовать, unserialize, конвертировать обратно в закрытие. Heck, учитывая, что Python утка напечатана, если вы перегрузили оператор вызова функции класса, чтобы он выглядел как функция, вам даже не нужно было бы преобразовывать его обратно в закрытие, а код, использующий его, не знал бы разница. Если у вас есть какие-либо гуру API-анализа API Python, пожалуйста, говорите.
Ответы
Ответ 1
Если вы просто используете класс с методом __call__
для начала, он должен работать плавно с pickle
.
class foo(object):
def __init__(self, bar, baz):
self.baz = baz
def __call__(self,waldo):
return self.baz * waldo
С другой стороны, хак, который преобразовал замыкание в экземпляр нового класса, созданного во время выполнения, не сработает, поскольку способ pickle
имеет дело с классами и экземплярами. pickle
не хранит классы; только имя модуля и имя класса. При чтении экземпляра или класса он пытается импортировать модуль и найти в нем нужный класс. Если вы использовали класс, созданный "на лету", вам не повезло.
Ответ 2
PiCloud выпустила pickler с открытым исходным кодом (LGPL), который может работать с закрытием функции и намного более полезным материалом. Он может использоваться независимо от инфраструктуры облачных вычислений - это просто обычный сортировщик. Весь shebang документирован здесь, и вы можете загрузить код через 'pip install cloud'. Во всяком случае, он делает то, что вы хотите. Продемонстрируем, что, замачивая замыкание:
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)
Теперь мы имеем closey
, исходное закрытие и closey2
, тот, который был восстановлен из сериализации строк. Пусть тестируются.
>>> closey(4)
20
>>> closey2(4)
20
Красивая. Модуль - чистый python - вы можете открыть его и легко увидеть, что заставляет магию работать. (Ответ - много кода.)
Ответ 3
Да! Я получил (по крайней мере, я думаю), то есть более общую проблему расчёта функции. Python так замечателен:), я узнал об этом, хотя функция dir() и несколько веб-поисков. Также замечательно, что он [надеюсь,] решил, я тоже нуждался в этом.
Я не проводил много испытаний на то, насколько надежным является эта функция co_code (вложенные fcns и т.д.), и было бы неплохо, если бы кто-то мог посмотреть, как подключить Python, чтобы функции могли автоматически травиться (например, они иногда могут быть замыкающими args).
Модуль 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)
Файл 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 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))
приветствия,
Николай
EDIT - для реальных случаев требуется более надежная глобальная обработка. fcn.func_code.co_names перечисляет глобальные имена.
Ответ 4
Рецепт 500261: Именованные кортежи содержит функцию, которая определяет класс "на лету". И этот класс поддерживает травление.
Вот суть:
result.__module__ = _sys._getframe(1).f_globals.get('__name__', '__main__')
В сочетании с предложением @Greg Ball для создания нового класса во время выполнения он может ответить на ваш вопрос.
Ответ 5
#!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)