Czy istnieje łatwy sposób na wytrawienie funkcji Pythona (lub w inny sposób serializowanie jej kodu)?

100

Próbuję przenieść funkcję przez połączenie sieciowe (przy użyciu asyncore). Czy istnieje łatwy sposób serializacji funkcji Pythona (takiej, która przynajmniej w tym przypadku nie będzie miała żadnych skutków ubocznych) do takiego transferu?

Idealnie chciałbym mieć parę funkcji podobnych do tych:

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

def receive():
    [receive obj from the network]
    func = pickle.loads(s)
    func()
Michael Fairley
źródło

Odpowiedzi:

120

Możesz serializować kod bajtowy funkcji, a następnie zrekonstruować go w obiekcie wywołującym. Marszałek moduł może być stosowane do przedmiotów uszeregować kodów, które mogą być ponownie zmontowane w funkcji. to znaczy:

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

Następnie w procesie zdalnym (po przesłaniu code_string):

import marshal, types

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

func(10)  # gives 100

Kilka zastrzeżeń:

  • Format marszałka (w tym przypadku dowolny kod bajtowy Pythona) może nie być kompatybilny między głównymi wersjami Pythona.

  • Działa tylko w przypadku implementacji cpython.

  • Jeśli funkcja odwołuje się do elementów globalnych (w tym zaimportowanych modułów, innych funkcji itp.), Które musisz odebrać, musisz je również serializować lub odtworzyć po stronie zdalnej. Mój przykład podaje tylko globalną przestrzeń nazw zdalnego procesu.

  • Prawdopodobnie będziesz musiał zrobić trochę więcej, aby obsługiwać bardziej złożone przypadki, takie jak zamknięcia lub funkcje generatora.

Brian
źródło
1
W Pythonie 2.5 „nowy” moduł jest przestarzały. „Nowa.funkcja” powinna zostać zastąpiona przez „Typy.Funkcji”, po „Typach importu”, jak sądzę.
Eric O Lebigot
2
Dzięki. To jest dokładnie to, czego szukałem. Na podstawie pobieżnych testów działa tak, jak w przypadku generatorów.
Michael Fairley
2
Jeśli czytasz kilka pierwszych akapitów modułu marszałka, widzisz, że zdecydowanie sugeruje to użycie marynaty? To samo dotyczy strony z marynatami. docs.python.org/2/library/marshal.html
dgorissen
1
Próbuję zastosować marshalmoduł do serializacji słownika słowników zainicjowanych jako defaultdict(lambda : defaultdict(int)). Ale zwraca błąd ValueError: unmarshallable object. Uwaga: używam pythona 2.7. Dowolny pomysł? Dzięki
user17375
2
W Pythonie 3.5.3 foo.func_codepodnosi AttributeError. Czy jest inny sposób uzyskania kodu funkcji?
AlQuemist
41

Sprawdź Dill , który rozszerza bibliotekę pickle Pythona o obsługę większej liczby typów, w tym funkcji:

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

Obsługuje również odniesienia do obiektów w zamknięciu funkcji:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3
Josh Rosen
źródło
2
dill również całkiem nieźle radzi sobie z pobieraniem kodu źródłowego z funkcji i lambd i zapisywaniem ich na dysku, jeśli wolisz to zamiast wytrawiania obiektów.
Mike McKerns
14

Pyro jest w stanie zrobić to za Ciebie .

RichieHindle
źródło
Musiałbym trzymać się standardowej biblioteki dla tego konkretnego projektu.
Michael Fairley
21
Ale to nie znaczy, że nie możesz spojrzeć na kod Pyro, aby zobaczyć, jak to jest zrobione :)
Aaron Digulla
4
@ AaronDigulla- prawda, ale warto nadmienić, że przed przeczytaniem choćby jednej linijki cudzego kodu, należy zawsze sprawdzić licencję oprogramowania. Czytanie cudzego kodu i ponowne wykorzystanie pomysłów bez cytowania źródła lub przestrzegania ograniczeń licencji / kopiowania może w wielu przypadkach zostać uznane za plagiat i / lub naruszenie praw autorskich.
mdscruggs
12

Najprostszym sposobem jest prawdopodobnie inspect.getsource(object)(zobacz moduł inspect ), który zwraca String z kodem źródłowym funkcji lub metody.

Aaron Digulla
źródło
Wygląda to dobrze, z wyjątkiem tego, że nazwa funkcji jest jawnie zdefiniowana w kodzie, co jest nieco problematyczne. Mógłbym usunąć pierwszą linię kodu, ale można to złamać, wykonując coś takiego jak „def \ / n func ():”. Mógłbym połączyć nazwę funkcji z samą funkcją, ale nie miałbym gwarancji, że nazwa się nie koliduje, albo musiałbym umieścić funkcję w opakowaniu, co nadal nie jest najczystszym rozwiązaniem, ale to może być konieczne.
Michael Fairley
1
Zwróć uwagę, że moduł inspect w rzeczywistości pyta funkcję, gdzie została zdefiniowana, a następnie wczytuje te wiersze z pliku kodu źródłowego - mało wyszukane.
za dużo php
1
Nazwę funkcji można znaleźć za pomocą jej atrybutu .__ name__. Możesz zastąpić wyrażenie regularne na ^ def \ s * {name} \ s * (i nadać mu dowolną nazwę. Nie jest to niezawodne, ale będzie działać w większości rzeczy.
za dużo php
6

Wszystko zależy od tego, czy generujesz funkcję w czasie wykonywania, czy nie:

Jeśli to zrobisz - inspect.getsource(object)nie będzie działać dla funkcji generowanych dynamicznie, ponieważ pobiera źródło obiektu z .pypliku, więc jako źródło można pobrać tylko funkcje zdefiniowane przed wykonaniem.

A jeśli twoje funkcje i tak są umieszczone w plikach, dlaczego nie dać odbiorcom dostępu do nich i przekazywać tylko nazwy modułów i funkcji.

Jedynym rozwiązaniem dla dynamicznie tworzonych funkcji, które przychodzi mi do głowy, jest skonstruowanie funkcji jako łańcucha przed transmisją, nadaniem źródła, a następnie eval()po stronie odbiornika.

Edycja: marshalrozwiązanie wygląda również całkiem sprytnie, nie wiedziałem, że możesz serializować coś innego niż wbudowane

kurczak
źródło
2
code_string = '' '
def foo (x):
    powrót x * 2
def bar (x):
    powrót x ** 2
'' '

obj = pickle.dumps (ciąg_kodowy)

Teraz

exec (pickle.loads (obj))

foo (1)
> 2
bar (3)
> 9
Yanni Papadakis
źródło
2

Możesz to zrobić:

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

Teraz transmit(fn_generator())wyśle ​​rzeczywistą definicję fn(x,y)zamiast odniesienia do nazwy modułu.

Możesz użyć tej samej sztuczki do wysyłania klas przez sieć.

Fardin
źródło
1

Podstawowe funkcje używane w tym module obejmują zapytanie, a ponadto zapewniają najlepszą kompresję w sieci; zobacz pouczający kod źródłowy:

y_serial.py module :: hurtownia obiektów Pythona z SQLite

„Serializacja + trwałość: w kilku linijkach kodu skompresuj i dodaj adnotacje do obiektów Pythona do SQLite, a następnie odzyskaj je chronologicznie za pomocą słów kluczowych bez żadnego SQL. Najbardziej przydatny„ standardowy ”moduł bazy danych do przechowywania danych bez schematu.”

http://yserial.sourceforge.net


źródło
1

Cloudpickle jest prawdopodobnie tym, czego szukasz. Cloudpickle jest opisana w następujący sposób:

Cloudpickle jest szczególnie przydatna do przetwarzania w klastrach, w przypadku których kod Pythona jest wysyłany przez sieć w celu wykonania na zdalnych hostach, prawdopodobnie blisko danych.

Przykład użycia:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)
Simon C.
źródło
0

Oto klasa pomocnicza, której możesz użyć do zawijania funkcji, aby były one pikowalne. Zastrzeżenia, o których już wspomniano, marshalbędą miały zastosowanie, ale staramy się używać marynaty, gdy tylko jest to możliwe. Nie podejmuje się żadnych wysiłków, aby zachować wartości globalne lub zamknięcia podczas serializacji.

    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)
memeplex
źródło