Python сериализует лексические замыкания?

Есть ли способ сериализовать лексическое замыкание в Python с помощью стандартной библиотеки? pickle и marshal не работают с лексическими замыканиями. Меня не волнуют подробности сериализации двоичных и строковых данных и т. д., это просто должно работать. Например:

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

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

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


person dsimcha    schedule 21.02.2009    source источник
comment
Пример кода, в котором определено лексическое замыкание, может помочь   -  person jfs    schedule 21.02.2009
comment
Знаете ли вы, как сбросить экземпляры обычных функций уровня модуля в файл и прочитать их обратно?   -  person jfs    schedule 21.02.2009
comment
Я бы предположил, что вы просто делаете это с Пиклом или чем-то еще. Я достаточно опытный программист, но очень новичок в Python, поэтому я понимаю сериализацию и лексические замыкания, но очень мало понимаю, как работает сам Python.   -  person dsimcha    schedule 21.02.2009
comment
Единственные варианты, которые я знаю даже для обычных функций: 1. сохранить как источник (*.py)/загрузить с использованием import (или execfile, eval и т. д.) 2. сохранить как объект упорядоченного кода (например, *.pyc)/загрузить использование маршала для преобразования строки в объект кода и его выполнения (или eval и т. д.).   -  person jfs    schedule 21.02.2009


Ответы (4)


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

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

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

person Greg Ball    schedule 22.02.2009

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

Красивый. Модуль написан на чистом питоне — вы можете открыть его и легко увидеть, что заставляет магию работать. (Ответ — много кода.)

person dan mackinlay    schedule 08.11.2010
comment
Потрясающий! Спасибо за ссылку! - person luqui; 20.08.2013

Да! Я понял (по крайней мере, я так думаю) - то есть более общую проблему травления функции. 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))

здравствуйте,
Николас

EDIT – для реальных случаев необходима более надежная глобальная обработка. fcn.func_code.co_names перечисляет глобальные имена.

person gatoatigrado    schedule 01.04.2009

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

Вот суть:

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

В сочетании с предложением @Greg Ball создать новый класс во время выполнения он может ответить ваш вопрос.

person jfs    schedule 24.02.2009