Как распиливать функцию python со своими зависимостями?

В качестве продолжения этого вопроса: Есть ли простой способ раскрыть функцию python (или иначе сериализовать его код)?

Я хотел бы увидеть пример этой пули из вышеупомянутого сообщения:

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

У меня есть простой тест, когда я пишу код байта функций в файл с помощью маршала:

def g(self,blah): 
    print blah

def f(self):
    for i in range(1,5):
        print 'some function f'
        g('some string used by g')

data = marshal.dumps(f.func_code)

file = open('/tmp/f2.txt', 'w')
file.write(data)

Затем, начиная с нового экземпляра python, я:

file = open('/tmp/f2.txt', 'r')
code = marshal.loads(file.read())
func2 = types.FunctionType(code, globals(), "some_func_name");
func2('blah')

В результате получается:

NameError: global name 'g' is not defined

Это не зависит от различных подходов, которые я применил к включению g. Я пробовал в основном такой же подход к отправке g как f, но f все еще не вижу g. Как получить g в глобальное пространство имен, чтобы его можно было использовать f в процессе приема?

Кто-то также рекомендовал посмотреть на пиро, как пример того, как это сделать. Я уже пытался попытаться понять соответствующий код в проекте дискотек. Я взял их класс dPickle и попытался воссоздать свои функции disco/tests/test_pickle.py в автономном приложении без успеха. У моего эксперимента были проблемы с выполнением функции маршалинга с помощью вызова дампов. В любом случае, возможно, исследование пиротехники будет следующим.

Таким образом, основная функциональность, которой я являюсь, - это возможность отправить метод по проводке и передать все основные методы "рабочей области" (например, g).

Пример с изменениями из ответа:

Рабочая функция_writer:

import marshal, types

def g(blah): 
    print blah


def f():
    for i in range(1,5):
        print 'some function f'
        g('blah string used by g')


f_data = marshal.dumps(f.func_code)
g_data = marshal.dumps(g.func_code);

f_file = open('/tmp/f.txt', 'w')
f_file.write(f_data)

g_file = open('/tmp/g.txt', 'w')
g_file.write(g_data)

Рабочая функция_процессор:

import marshal, types

f_file = open('/tmp/f.txt', 'r')
g_file = open('/tmp/g.txt', 'r')

f_code = marshal.loads(f_file.read())
g_code = marshal.loads(g_file.read())

f = types.FunctionType(f_code, globals(), 'f');
g = types.FunctionType(g_code, globals(), 'g');

f()

Ответ 1

Я пробовал в основном тот же подход к отправке g как f, но f все еще не может видеть g. Как получить g в глобальное пространство имен, чтобы его можно было использовать f в процессе приема?

Назначьте его глобальному имени g. (Я вижу, что вы назначаете f в func2, а не в f. Если вы делаете что-то подобное с g, тогда понятно, почему f не может найти g. разрешение имен происходит во время выполнения - g не просматривается, пока вы не вызываете f.)

Конечно, я предполагаю, что вы не указали код, который вы используете для этого.

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

sandbox = {}

with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        sandbox[code.co_name] = types.FunctionType(code, sandbox, code.co_name)

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

Внутри незакрашенных функций словарь песочницы - это globals(), поэтому внутри f(), g получает свое значение от sandbox["g"]. Чтобы вызвать f, тогда будет: sandbox["f"]("blah")

Ответ 2

Пакет облаков делает это - просто "pip install cloud", а затем:

import cloud, pickle
def foo(x): 
    return x*3
def bar(z): 
    return foo(z)+1
x = cloud.serialization.cloudpickle.dumps(bar)
del foo 
del bar
f = pickle.loads(x)
print f(3)  # displays "10"

Другими словами, просто вызовите cloudpickle.dump() или cloudpickle.dumps() так же, как вы используете pickle. *, а затем используйте нативный pickle.load() или pickle.loads() для оттаивания.

Picloud выпустил пакет "облачный" python под LGPL, и другие проекты с открытым исходным кодом уже используют его (google для "cloudpickle.py", чтобы увидеть несколько). Документация на picloud.com дает вам представление о том, насколько мощным является этот код, и почему у них появился стимул приложить усилия к разработке кодового траления общего назначения - весь их бизнес строится вокруг него. Идея состоит в том, что если у вас есть cpu_intensive_function() и вы хотите запустить ее на сетке Amazon EC2, вы просто замените:

cpu_intensive_function(some, args) 

с:

cloud.call(cpu_intensive_function, some, args)

Последний использует cloudpickle для разборки любого зависимого кода и данных, отправляет его в EC2, запускает его и возвращает результаты вам, когда вы вызываете cloud.result(). (Picloud счета в миллисекундах, это дешево, как черт, и я все время использую его для моделирования monte carlo и анализа временных рядов, когда мне нужны сотни ядер процессора всего несколько секунд. Я не могу сказать достаточно хорошего вещи об этом, и я даже не работаю там.)

Ответ 3

Вы можете получить лучший дескриптор глобальных объектов, импортировав __main__ и используя методы, доступные в этом модуле. Это то, что dill делает, чтобы сериализовать почти что-либо в python. В основном, когда укроп сериализует интерактивно определенную функцию, он использует некоторое управление именами на __main__ на стороне сериализации и десериализации, которая делает __main__ действительным модулем.

>>> import dill
>>> 
>>> def bar(x):
...   return foo(x) + x
... 
>>> def foo(x):
...   return x**2
... 
>>> bar(3)
12
>>> 
>>> _bar = dill.loads(dill.dumps(bar))
>>> _bar(3)
12

Фактически, укроп регистрирует его типы в реестре pickle, поэтому, если у вас есть код черного ящика, который использует pickle, и вы не можете его редактировать, то просто импортировать укроп можно магически заставить его работать без monkeypatching Сторонний код.

Или, если вы хотите, чтобы весь сеанс интерпретатора был отправлен как "образ python", укроп также может это сделать.

>>> # continuing from above
>>> dill.dump_session('foobar.pkl')
>>>
>>> ^D
[email protected]>$ python
Python 2.7.5 (default, Sep 30 2013, 20:15:49) 
[GCC 4.2.1 (Apple Inc. build 5566)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import dill
>>> dill.load_session('foobar.pkl')
>>> _bar(3)
12

Вы можете легко отправить изображение через ssh на другой компьютер и начать там, где вы остановились, пока существует совместимость версии рассола и обычные оговорки об изменении python и о том, что вы устанавливаете.

Ответ 4

Каждый модуль имеет свои собственные глобальные переменные, глобальных универсалов нет. Мы можем "внедрить" восстановленные функции в некоторый модуль и использовать его как обычный модуль.

- сохранить -

import marshal
def f(x):
    return x + 1
def g(x):
    return f(x) ** 2
funcfile = open("functions.pickle", "wb")
marshal.dump(f.func_code, funcfile)
marshal.dump(g.func_code, funcfile)
funcfile.close()

- восстановить -

import marshal
import types
open('sandbox.py', 'w').write('')  # create an empty module 'sandbox'
import sandbox
with open("functions.pickle", "rb") as funcfile:
    while True:
        try:
            code = marshal.load(funcfile)
        except EOFError:
             break
        func = types.FunctionType(code, sandbox.__dict__, code.co_name)
        setattr(sandbox, code.co_name, func)   # or sandbox.f = ... if the name is fixed
assert sandbox.g(3) == 16   # f(3) ** 2
# it is possible import them from other modules
from sandbox import g

Отредактировано:
Вы также можете импортировать некоторый модуль .e.g. "sys" в пространство имен "песочница" снаружи:

sandbox.sys = __import__('sys')

или то же самое:

exec 'import sys' in sandbox.__dict__
assert 'sys' in sandbox, 'Verify imported into sandbox'

Исходный код будет работать, если вы делаете это не в интерактивном ipython, а в программе python или в обычном python-взаимодействии!!!

Ipython использует некоторое странное пространство имен, которое не является dict любого модуля из sys.modules. Обычный python или любая основная программа используют sys.modules['__main__'].__dict__ как globals(). В любом модуле используется that_module.__dict__, что тоже нормально, только проблема с ipython.

Ответ 5

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