2014-10-08 10 views
6

Próbuję serializować funkcje Pythona (kod + zamknięcia) i przywrócić je później. Używam kodu u dołu tego wpisu.Czy mogę przywrócić funkcję, której zamknięcie zawiera cykle w języku Python?

To jest bardzo elastyczny kod. Pozwala to szeregowanie i deserialisation funkcji wewnętrznych i funkcje, które są zamknięcia, takie jak te, które potrzebują ich kontekst zostać przywrócone:

def f1(arg): 
    def f2(): 
     print arg 

    def f3(): 
     print arg 
     f2() 

    return f3 

x = SerialiseFunction(f1(stuff)) # a string 
save(x) # save it somewhere 

# later, possibly in a different process 

x = load() # get it from somewhere 
newf2 = DeserialiseFunction(x) 
newf2() # prints value of "stuff" twice 

te połączenia będą działać, nawet jeśli istnieją funkcje w zamknięciu swojej funkcji, funkcjonuje w ich zamknięciach i tak dalej (mamy wykres zamknięć, w których zamknięcia zawierają funkcje, które mają zamknięcia, które zawierają więcej funkcji itd.).

Jednak okazuje się, że te wykresy mogą zawierać cykle:

def g1(): 
    def g2(): 
     g2() 
    return g2() 

g = g1() 

Jeśli patrzę na zamknięciu g2 „s (poprzez g), widzę g2 w nim:

>>> g 
<function g2 at 0x952033c> 
>>> g.func_closure[0].cell_contents 
<function g2 at 0x952033c> 

To powoduje poważny problem, gdy próbuję deserializować funkcję, ponieważ wszystko jest niezmienne. Co muszę zrobić, aby funkcję newg2:

newg2 = types.FunctionType(g2code, globals, closure=newg2closure) 

gdzie newg2closure tworzony jest w następujący sposób:

newg2closure = (make_cell(newg2),) 

co oczywiście nie może być wykonana; każda linia kodu opiera się na drugiej. Komórki są niezmienne, krotki są niezmienne, typy funkcji są niezmienne.

Więc próbuję się dowiedzieć, czy istnieje sposób na stworzenie powyższego newg2? Czy istnieje sposób, w jaki mogę utworzyć obiekt typu funkcji, w którym obiekt jest wymieniony na jego własnym wykresie zamknięcia?

Używam Pythona 2.7 (jestem na App Engine, więc nie mogę przejść do Pythona 3).


Dla porównania, moje funkcje serializacji:

def SerialiseFunction(aFunction): 
    if not aFunction or not isinstance(c, types.FunctionType): 
     raise Exception ("First argument required, must be a function") 

    def MarshalClosureValues(aClosure): 
     logging.debug(repr(aClosure)) 
     lmarshalledClosureValues = [] 
     if aClosure: 
      lclosureValues = [lcell.cell_contents for lcell in aClosure] 
      lmarshalledClosureValues = [ 
       [marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure)] if hasattr(litem, "func_code") 
       else [marshal.dumps(litem)] 
       for litem in lclosureValues 
      ] 
     return lmarshalledClosureValues 

    lmarshalledFunc = marshal.dumps(aFunction.func_code) 
    lmarshalledClosureValues = MarshalClosureValues(aFunction.func_closure) 
    lmoduleName = aFunction.__module__ 

    lcombined = (lmarshalledFunc, lmarshalledClosureValues, lmoduleName) 

    retval = marshal.dumps(lcombined) 

    return retval 


def DeserialiseFunction(aSerialisedFunction): 
    lmarshalledFunc, lmarshalledClosureValues, lmoduleName = marshal.loads(aSerialisedFunction) 

    lglobals = sys.modules[lmoduleName].__dict__ 

    def make_cell(value): 
     return (lambda x: lambda: x)(value).func_closure[0] 

    def UnmarshalClosureValues(aMarshalledClosureValues): 
     lclosure = None 
     if aMarshalledClosureValues: 
      lclosureValues = [ 
        marshal.loads(item[0]) if len(item) == 1 
        else types.FunctionType(marshal.loads(item[0]), lglobals, closure=UnmarshalClosureValues(item[1])) 
        for item in aMarshalledClosureValues if len(item) >= 1 and len(item) <= 2 
       ] 
      lclosure = tuple([make_cell(lvalue) for lvalue in lclosureValues]) 
     return lclosure 

    lfunctionCode = marshal.loads(lmarshalledFunc) 
    lclosure = UnmarshalClosureValues(lmarshalledClosureValues) 
    lfunction = types.FunctionType(lfunctionCode, lglobals, closure=lclosure) 
    return lfunction 
+1

To jest bardzo ciekawe pytanie i nie wiem wystarczająco dużo, aby na nie odpowiedzieć. Czy pomogłoby to w ogóle w C api? Widziałem, jak ludzie zmieniają zamknięcia w ten sposób przed a la http://pythondoeswhat.blogspot.com/2012/11/back-porting-non-local.html Oczywiście, kiedy uruchamiam ten kod, po prostu kończę z a segfault, więc nie jestem pewien, jak legalne jest to łącze. –

+0

Czytałem kod C dla Pythona, frustrująco możesz sprawić, żeby działało na tym poziomie - wszystkie obiekty są zmienne - ale nie mogę się tam dostać. Jestem nastawiony na appengine, więc myślę, że wszystko co robię musi być czystym pytonem. –

+0

Uwaga: Nie można zastąpić własnych obiektów zamiast komórek. Próbowałem :-) . Implementacja type.FunctionType() sprawdza dokładnie, czy przekazałeś mu krotkę komórek, bez pisania na maszynie. –

Odpowiedz

3

Oto metoda, która działa.

Nie można naprawić tych niezmiennych obiektów, ale można w tym miejscu użyć funkcji proxy zamiast okrągłych odniesień i sprawić, aby sprawdzały rzeczywistą funkcję w globalnym słowniku.

1: Podczas szeregowania monitoruj wszystkie funkcje, które widziałeś. Jeśli zobaczysz to samo ponownie, nie resiseruj, zamiast serializować wartość wartownika.

Użyłem zestawu:

lfunctionHashes = set() 

i dla każdej pozycji odcinkach, sprawdź, czy jest w zestawie, iść z Sentinel Jeśli tak, w przeciwnym razie dodać go do zestawu i marszałka prawidłowo:

lhash = hash(litem) 
if lhash in lfunctionHashes: 
    lmarshalledClosureValues.append([lhash, None]) 
else: 
    lfunctionHashes.add(lhash) 
    lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__]) 

2: Kiedy deserialising zachować globalną dict z functionhash: funkcja

gfunctions = {} 

Podczas deserial podczas deserializacji funkcji, dodaj ją do gfunctions. Tutaj pozycja jest (hash, kod, closurevalues, modulename):

lfunction = types.FunctionType(marshal.loads(item[1]), globals, closure=UnmarshalClosureValues(item[2])) 
gfunctions[item[0]] = lfunction 

A kiedy można natknąć wartości wskaźnikowych dla funkcji, należy skorzystać z serwera proxy, przekazując mieszania funkcji:

lfunction = make_proxy(item[0]) 

Oto proxy. Wygląda się rzeczywistą funkcję w oparciu o hash:

def make_proxy(f_hash): 
    def f_proxy(*args, **kwargs): 
     global gfunctions 
     f = lfunctions[f_hash] 
     f(*args, **kwargs) 

    return f_proxy 

miałem też dokonać kilku zmian:

  • mam używane marynatę zamiast marszałka w niektórych miejscach może zbadać to dalej
  • Załączam nazwę modułu funkcji w serializacji, jak również kod i zamknięcie, więc mogę wyszukać poprawne globale dla funkcji, gdy deserialising.
  • W deserialisation, długość krotki mówi ci, co masz deserialising: 1 dla prostych wartości, 2 dla funkcji, która wymaga proxy, 4 dla w pełni serializowanym funkcji

Oto pełna nowy kod.

lfunctions = {} 

def DeserialiseFunction(aSerialisedFunction): 
    lmarshalledFunc, lmarshalledClosureValues, lmoduleName = pickle.loads(aSerialisedFunction) 

    lglobals = sys.modules[lmoduleName].__dict__ 
    lglobals["lfunctions"] = lfunctions 

    def make_proxy(f_hash): 
     def f_proxy(*args, **kwargs): 
      global lfunctions 
      f = lfunctions[f_hash] 
      f(*args, **kwargs) 

     return f_proxy 

    def make_cell(value): 
     return (lambda x: lambda: x)(value).func_closure[0] 

    def UnmarshalClosureValues(aMarshalledClosureValues): 
     global lfunctions 

     lclosure = None 
     if aMarshalledClosureValues: 
      lclosureValues = [] 
      for item in aMarshalledClosureValues: 
       ltype = len(item) 
       if ltype == 1: 
        lclosureValues.append(pickle.loads(item[0])) 
       elif ltype == 2: 
        lfunction = make_proxy(item[0]) 
        lclosureValues.append(lfunction) 
       elif ltype == 4: 
        lfuncglobals = sys.modules[item[3]].__dict__ 
        lfuncglobals["lfunctions"] = lfunctions 
        lfunction = types.FunctionType(marshal.loads(item[1]), lfuncglobals, closure=UnmarshalClosureValues(item[2])) 
        lfunctions[item[0]] = lfunction 
        lclosureValues.append(lfunction) 
      lclosure = tuple([make_cell(lvalue) for lvalue in lclosureValues]) 
     return lclosure 

    lfunctionCode = marshal.loads(lmarshalledFunc) 
    lclosure = UnmarshalClosureValues(lmarshalledClosureValues) 
    lfunction = types.FunctionType(lfunctionCode, lglobals, closure=lclosure) 
    return lfunction 

def SerialiseFunction(aFunction): 
    if not aFunction or not hasattr(aFunction, "func_code"): 
     raise Exception ("First argument required, must be a function") 

    lfunctionHashes = set() 

    def MarshalClosureValues(aClosure, aParentIndices = []): 
     lmarshalledClosureValues = [] 
     if aClosure: 
      lclosureValues = [lcell.cell_contents for lcell in aClosure] 

      lmarshalledClosureValues = [] 
      for index, litem in enumerate(lclosureValues): 
       lfullIndex = list(aParentIndices) 
       lfullIndex.append(index) 

       if isinstance(litem, types.FunctionType): 
        lhash = hash(litem) 
        if lhash in lfunctionHashes: 
         lmarshalledClosureValues.append([lhash, None]) 
        else: 
         lfunctionHashes.add(lhash) 
         lmarshalledClosureValues.append([lhash, marshal.dumps(litem.func_code), MarshalClosureValues(litem.func_closure, lfullIndex), litem.__module__]) 
       else: 
        lmarshalledClosureValues.append([pickle.dumps(litem)]) 

    lmarshalledFunc = marshal.dumps(aFunction.func_code) 
    lmarshalledClosureValues = MarshalClosureValues(aFunction.func_closure) 
    lmoduleName = aFunction.__module__ 

    lcombined = (lmarshalledFunc, lmarshalledClosureValues, lmoduleName) 

    retval = pickle.dumps(lcombined) 

    return retval 
+0

Nie potrzebujesz globalnego wyszukiwania, jeśli po prostu tworzysz zmienną nielokalną (np. Listę 1-elementową) wewnątrz 'make_proxy', do której wstrzykujesz funkcję. To powinno zaoszczędzić czasy poszukiwań, jeśli kiedykolwiek ma to znaczenie. Ale dobre myślenie. – Veedrac