Python сериализует лексические замыкания? - PullRequest
19 голосов
/ 21 февраля 2009

Есть ли способ сериализации лексического замыкания в Python с использованием стандартной библиотеки? Рассол и маршал не работают с лексическими пробками. Меня не волнуют детали двоичной и строковой сериализации и т. Д., Это просто должно работать. Например:

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

Я хотел бы просто иметь возможность сбросить экземпляры замыкания в файл и прочитать их обратно.

Edit: Один из относительно очевидных способов решения этой проблемы заключается в использовании некоторых методов отражения для преобразования лексических замыканий в объекты классов и наоборот. Затем можно преобразовать в классы, сериализовать, десериализовать, преобразовать обратно в замыкания. Черт возьми, учитывая, что Python типизирован по утке, если вы перегрузите оператор вызова функции класса, чтобы он выглядел как функция, вам даже не нужно будет преобразовывать его обратно в замыкание, а код, использующий его, не будет знать различия. Если есть какие-либо гуру API Python для отражения, пожалуйста, говорите.

Ответы [ 5 ]

20 голосов
/ 08 ноября 2010

PiCloud выпустил сборщик с открытым исходным кодом (LGPL), который может обрабатывать закрытие функций и многое другое полезное. Он может использоваться независимо от их инфраструктуры облачных вычислений - это просто обычный сборщик. Весь шебанг задокументирован здесь , и вы можете скачать код через «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 - вы можете открыть его и легко увидеть, что заставляет магию работать. (Ответ много кода.)

15 голосов
/ 22 февраля 2009

Если вы просто используете класс с методом __call__ для начала, все должно работать гладко с pickle.

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

С другой стороны, хак, который преобразовал замыкание в экземпляр нового класса, созданного во время выполнения, не будет работать, поскольку pickle работает с классами и экземплярами. pickle не хранит классы; только имя модуля и имя класса. При чтении экземпляра или класса он пытается импортировать модуль и найти в нем требуемый класс. Если вы использовали класс, созданный на лету, вам не повезло.

1 голос
/ 01 апреля 2009

Да! Я понял (по крайней мере, я так думаю), то есть более общая проблема выбора функции. Python такой замечательный :), я узнал большинство из них, используя функцию dir () и пару поисков в сети. Также замечательно, что это [надеюсь] решено, мне это тоже нужно.

Я не проводил много тестов на предмет того, насколько надежна эта функция co_code (вложенные fcns и т. Д.), И было бы неплохо, если бы кто-то мог посмотреть, как перехватить Python, чтобы функции могли автоматически выбираться (например, они иногда могут быть закрывающие аргументы).

Модуль 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'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))

ура,
Николай

РЕДАКТИРОВАТЬ - для реальных случаев необходима более надежная глобальная обработка. fcn.func_code.co_names перечисляет глобальные имена.

0 голосов
/ 19 марта 2010
#!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)
0 голосов
/ 25 февраля 2009

Рецепт 500261: Именованные кортежи содержит функцию, которая определяет класс на лету. И этот класс поддерживает травление.

Вот суть:

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

В сочетании с @ предложением Грега Болла создать новый класс во время выполнения, это может ответить на ваш вопрос.

...