Есть ли простой способ обработать функцию Python (или иначе сериализовать ее код)?

100

Я пытаюсь передать функцию через сетевое соединение (используя asyncore). Есть ли простой способ сериализации функции Python (которая, по крайней мере, в этом случае не будет иметь побочных эффектов) для такой передачи?

В идеале я бы хотел иметь пару функций, подобных этим:

def transmit(func):
    obj = pickle.dumps(func)
    [send obj across the network]

def receive():
    [receive obj from the network]
    func = pickle.loads(s)
    func()
Майкл Фэйрли
источник

Ответы:

120

Вы можете сериализовать байт-код функции, а затем восстановить его в вызывающей стороне. Модуль маршалинга может использоваться для сериализации объектов кода, которые затем могут быть повторно собраны в функцию. то есть:

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.func_code)

Затем в удаленном процессе (после передачи code_string):

import marshal, types

code = marshal.loads(code_string)
func = types.FunctionType(code, globals(), "some_func_name")

func(10)  # gives 100

Несколько предостережений:

  • Формат маршала (любой байт-код Python в этом отношении) может быть несовместим между основными версиями Python.

  • Будет работать только для реализации cpython.

  • Если функция ссылается на глобальные объекты (включая импортированные модули, другие функции и т. Д.), Которые вам нужно подобрать, вам также необходимо сериализовать их или воссоздать их на удаленной стороне. В моем примере просто указано глобальное пространство имен удаленного процесса.

  • Вероятно, вам нужно будет сделать немного больше для поддержки более сложных случаев, таких как замыкания или функции генератора.

Брайан
источник
1
В Python 2.5 «новый» модуль устарел. Я считаю, что 'new.function' следует заменить на 'types.FunctionType' после «типов импорта».
Эрик О Лебигот,
2
Спасибо. Это именно то, что я искал. По результатам беглого тестирования, он работает так же, как и для генераторов.
Майкл Фэрли,
2
Если вы прочтете первую пару абзацев модуля маршала, вы увидите, что вместо этого настоятельно рекомендуется использовать pickle? То же самое для страницы рассола. docs.python.org/2/library/marshal.html
dgorissen,
1
Я пытаюсь применить marshalмодуль для сериализации словаря словарей, инициализированных как defaultdict(lambda : defaultdict(int)). Но возвращает ошибку ValueError: unmarshallable object. Обратите внимание: я использую python2.7. Любая идея? Спасибо
user17375 08
2
На Python 3.5.3 foo.func_codeвызывает AttributeError. Есть ли другой способ получить код функции?
AlQuemist
41

Обратите внимание на Dill , который расширяет библиотеку pickle Python для поддержки большего количества типов, включая функции:

>>> import dill as pickle
>>> def f(x): return x + 1
...
>>> g = pickle.dumps(f)
>>> f(1)
2
>>> pickle.loads(g)(1)
2

Он также поддерживает ссылки на объекты в закрытии функции:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3
Джош Розен
источник
2
dill также неплохо справляется с получением исходного кода из функций и лямбда-выражений и сохранением их на диск, если вы предпочитаете это травлению объектов.
Майк МакКернс
14

Поджигатель может сделать это за вас .

РичиХиндл
источник
Для этого конкретного проекта мне нужно придерживаться стандартной библиотеки.
Майкл Фэрли
21
Но это не значит, что вы не можете посмотреть на код Pyro, чтобы увидеть, как это делается :)
Аарон Дигулла
4
@ AaronDigulla - правда, но стоит упомянуть, что перед чтением одной строчки чужого опубликованного кода всегда следует проверять лицензию на программное обеспечение. Чтение чужого кода и повторное использование идей без ссылки на источник или соблюдения ограничений лицензии / копирования во многих случаях может рассматриваться как плагиат и / или нарушение авторских прав.
mdscruggs
12

Самый простой способ - это, вероятно inspect.getsource(object)(см. Модуль inspect ), который возвращает строку с исходным кодом для функции или метода.

Аарон Дигулла
источник
Это выглядит хорошо, за исключением того, что имя функции явно определено в коде, что немного проблематично. Я мог бы удалить первую строку кода, но ее можно разбить, выполнив что-то вроде 'def \ / n func ():'. Я мог бы связать имя функции с самой функцией, но у меня не было бы никаких гарантий, что имя не будет конфликтовать, или мне пришлось бы поместить функцию в оболочку, что все еще не является самым чистым решением, но это может быть необходимо.
Майкл Фэрли,
1
Обратите внимание, что модуль inspect на самом деле просто запрашивает функцию, в которой он был определен, а затем считывает эти строки из файла исходного кода - это вряд ли сложно.
слишком много php
1
Вы можете узнать имя функции, используя ее атрибут .__ name__. Вы можете заменить регулярное выражение на ^ def \ s * {name} \ s * (и дать ему любое имя, которое вам нравится. Это не надежно, но сработает для большинства вещей.
слишком много php
6

Все зависит от того, генерируете ли вы функцию во время выполнения или нет:

Если вы это сделаете - inspect.getsource(object)не будет работать для динамически генерируемых функций, поскольку он получает источник объекта из .pyфайла, поэтому только функции, определенные до выполнения, могут быть извлечены в качестве источника.

И если ваши функции все равно помещаются в файлы, почему бы не предоставить доступ к ним получателю и передать только имена модулей и функций.

Единственное решение для динамически создаваемых функций, которое я могу придумать, - это создать функцию в виде строки перед передачей, источником передачи, а затем eval()на стороне приемника.

Изменить: marshalрешение выглядит также довольно умным, не знал, что вы можете сериализовать что-то другое, встроенное

Курчак
источник
2
code_string = '' '
def foo (x):
    вернуть x * 2
def bar (x):
    возврат x ** 2
'' '

obj = pickle.dumps (строка_кода)

Сейчас

exec (pickle.loads (объект))

фу (1)
> 2
бар (3)
> 9
Янни Пападакис
источник
2

Ты можешь сделать это:

def fn_generator():
    def fn(x, y):
        return x + y
    return fn

Теперь transmit(fn_generator())будет отправлено фактическое определение fn(x,y)вместо ссылки на имя модуля.

Вы можете использовать тот же прием для отправки классов по сети.

Фардин
источник
1

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

y_serial.py module :: складские объекты Python с SQLite

«Сериализация + постоянство :: в нескольких строках кода сжимайте и аннотируйте объекты Python в SQLite; а затем извлекайте их в хронологическом порядке по ключевым словам без какого-либо SQL. Самый полезный« стандартный »модуль для базы данных для хранения данных без схемы».

http://yserial.sourceforge.net


источник
1

Cloudpickle , вероятно, то, что вы ищете. Облако описывается следующим образом:

cloudpickle особенно полезен для кластерных вычислений, когда код Python доставляется по сети для выполнения на удаленных хостах, возможно, рядом с данными.

Пример использования:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)
Саймон С
источник
0

Вот вспомогательный класс, который вы можете использовать для обертывания функций, чтобы сделать их доступными для выбора. Уже упомянутые предостережения marshalприменимы, но по возможности стараемся использовать рассол. Не прилагается никаких усилий для сохранения глобальных объектов или замыканий при сериализации.

    class PicklableFunction:
        def __init__(self, fun):
            self._fun = fun

        def __call__(self, *args, **kwargs):
            return self._fun(*args, **kwargs)

        def __getstate__(self):
            try:
                return pickle.dumps(self._fun)
            except Exception:
                return marshal.dumps((self._fun.__code__, self._fun.__name__))

        def __setstate__(self, state):
            try:
                self._fun = pickle.loads(state)
            except Exception:
                code, name = marshal.loads(state)
                self._fun = types.FunctionType(code, {}, name)
мемплекс
источник