Чи є простий спосіб вибрати функцію пітона (або іншим чином серіалізувати його код)?


100

Я намагаюся перенести функцію через мережеве з'єднання (використовуючи asyncore). Чи є простий спосіб серіалізувати функцію пітона (той, який, принаймні, не матиме жодних побічних ефектів) для такого перенесення?

Я в ідеалі хотів би мати пару функцій, подібних до таких:

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
Якщо ви прочитали перші кілька абзаців на модулі маршалки, ви побачите, що це настійно пропонує замість цього маринувати? Те саме для сторінки соління. docs.python.org/2/library/marshal.html
dgorissen

1
Я намагаюся застосувати marshalмодуль для серіалізації словника словників, ініціалізованого як defaultdict(lambda : defaultdict(int)). Але це повертає помилку ValueError: unmarshallable object. Примітка: Я у нас python2.7. Будь-яка ідея? Дякую
користувач17375

2
На Python 3.5.3, foo.func_codeпіднімає AttributeError. Чи є інший спосіб отримати код функції?
AlQuemist

41

Ознайомтеся з кропом , який розширює бібліотеку солінь 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
Кріп також виконує досить непогану роботу з отримання вихідного коду від функцій та лямбда та збереження їх на диску, якщо ви хочете, щоб це не було над засобом.
Майк Маккернс

14

Мені потрібно дотримуватися стандартної бібліотеки для цього конкретного проекту.
Майкл Ферлі

21
Але це не означає, що ви не можете подивитися на код Піро, щоб побачити, як це робиться :)
Аарон Дігулла,

4
@ AaronDigulla - правда, але варто згадати, що перед читанням одного рядка чужого опублікованого коду завжди слід перевірити ліцензію програмного забезпечення. Читання чужого коду та повторне використання ідей без посилання на джерело чи дотримання ліцензійних / копіювальних обмежень можна вважати плагіатом та / або порушенням авторських прав у багатьох випадках.
mdscruggs

12

Мабуть, найпростіший спосіб inspect.getsource(object)(див. Модуль перевірки ), який повертає рядок із вихідним кодом для функції чи методу.


Це виглядає добре, за винятком того, що ім'я функції чітко визначено в коді, що трохи проблематично. Я можу зняти перший рядок коду, але це зламається, виконуючи щось на кшталт 'def \ / n func ():'. Я міг назбирати назву функції із самою функцією, але я не мав би гарантій, що ім’я не зіткнеться, або мені доведеться помістити функцію в обгортку, що все ще не є найчистішим рішенням, але це, можливо, доведеться робити.
Майкл Ферлі

1
Зауважте, що модуль перевірки насправді просто запитує функцію, де він був визначений, а потім читати в цих рядках з файлу вихідного коду - навряд чи складно.
занадто багато php

1
Ви можете дізнатись ім'я функції за допомогою атрибута .__ name__. Ви можете замінити регулярний вираз на ^ def \ s * {name} \ s * (і дайте йому будь-яке ім’я. Вам це не дурно, але він працюватиме для більшості речей.
занадто багато php

6

Все залежить від того, ви генеруєте функцію під час виконання чи ні:

Якщо ви це зробите - inspect.getsource(object)не працюватимуть для динамічно генерованих функцій, оскільки вони отримують джерело об'єкта з .pyфайлу, тому лише джерела , визначені перед виконанням, можуть бути отримані як джерело.

А якщо ваші функції все-таки розміщуються у файлах, то чому б не надати отримувачам доступ до них, а лише пройти навколо імен модулів та функцій.

Єдине рішення для динамічно створених функцій, про які я можу придумати, - це побудувати функцію у вигляді рядка перед передачею, передачею джерела і потім eval()його на стороні приймача.

Редагувати: marshalрішення виглядає також досить розумним, не знав, що ви можете серіалізувати щось інше, що вбудовані



2
code_string = '' '
def foo (x):
    повернення х * 2
смуга захисту (х):
    повернення x ** 2
'' '

obj = pickle.dumps (code_string)

Тепер

exec (pickle.loads (obj))

foo (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 модуль :: зберігання об’єктів Python з SQLite

"Серіалізація + стійкість: у кількох рядках коду, стискайте та коментуйте об’єкти Python в SQLite; потім пізніше отримуйте їх хронологічно за ключовими словами без будь-якого SQL. Найкорисніший" стандартний "модуль для бази даних для зберігання даних без схем."

http://yserial.sourceforge.net


1

Cloudpickle - це, мабуть, те, що ви шукаєте. 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.