2014-10-08 2 views
6

Ich versuche, Python-Funktionen (Code + Closures) zu serialisieren und später wieder zu installieren. Ich verwende den Code am Ende dieses Posts.Kann ich eine Funktion wiederherstellen, deren Cycle-Zyklen in Python enthalten sind?

Dies ist sehr flexibler Code. Es ermöglicht die Serialisierung und Deserialisierung von inneren Funktionen und Funktionen, die Verschlüsse, wie zum Beispiel diejenigen, die ihren Kontext müssen wieder eingesetzt werden:

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 

Diese Anrufe auch dann funktionieren, wenn es Funktionen in der Schließung Ihrer Funktion sind, Funktionen in ihren Schließungen und so weiter (wir haben eine Grafik von Schließungen, wo Schließungen Funktionen enthalten, die Schließungen haben, die mehr Funktionen enthalten, und so weiter).

Es stellt sich jedoch heraus, dass diese Graphen Zyklen enthalten:

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

g = g1() 

Wenn ich g2 ‚s Schließung aussehen (via g), kann ich g2 darin sehen:

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

Dies verursacht ein ernsthaftes Problem, wenn ich versuche, die Funktion zu deserialisieren, da alles unveränderlich ist. Was muss ich tun, um die Funktion zu machen newg2:

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

wo newg2closure wird wie folgt erstellt:

newg2closure = (make_cell(newg2),) 

was natürlich nicht getan werden kann; Jede Codezeile beruht auf der anderen. Zellen sind unveränderlich, Tupel sind unveränderlich, Funktionstypen sind unveränderlich.

Also was ich herausfinden will ist, gibt es eine Möglichkeit, newg2 oben zu erstellen? Gibt es eine Möglichkeit, ein Objekt vom Funktionstyp zu erstellen, in dem das Objekt in einem eigenen Abschlussdiagramm erwähnt wird?

Ich benutze Python 2.7 (ich bin auf App Engine, so kann ich nicht zu Python 3 gehen).


Als Referenz meine Serialisierungsfunktionen:

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

Dies ist eine super interessante Frage und ich weiß nicht genug, um es zu beantworten. Würde es überhaupt helfen, in den CAPI zu gehen? Ich habe Leute gesehen, die Verschlüsse so vor aa ändern http://pythondoeshwhat.blogspot.com/2012/11/back-porting-non-local.html Natürlich, wenn ich diesen Code laufen lasse, komme ich gerade mit ein segfault, also bin ich mir nicht sicher, wie echt diese Verbindung ist. –

+0

Ich habe den C-Code für Python gelesen, frustrierend könnte man das auf dieser Ebene machen - alle Objekte sind änderbar - aber ich kann nicht hinein. Ich ziele auf appengine, also denke ich, dass alles, was ich tue, reine Python sein muss. –

+0

Ein Hinweis: Sie können nicht Ihre eigenen Objekte anstelle von Zellen ersetzen. Ich habe es versucht :-) . Die c-Implementierung von types.FunctionType() überprüft sorgfältig, dass Sie ein Tupel von Zellen übergeben haben, wobei keine Tipp-Eingabe erlaubt ist. –

Antwort

3

Hier ist eine Methode, die funktioniert.

Sie können diese unveränderlichen Objekte nicht reparieren, aber Sie können Proxy-Funktionen anstelle von Zirkelbezügen anhängen und die tatsächliche Funktion in einem globalen Wörterbuch nachschlagen lassen.

1: Behalten Sie bei der Serialisierung alle Funktionen im Auge, die Sie gesehen haben. Wenn Sie das gleiche erneut sehen, reserialisieren Sie nicht, sondern serialisieren Sie einen Sentinel-Wert.

Ich habe einen Satz verwendet:

lfunctionHashes = set() 

und für jedes serialisierten Element, zu überprüfen, ob es in der Menge ist, wenn dies mit einem Sentinel gehen, sonst fügen Sie ihn in die Menge und Marschall richtig:

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: Wenn deserialising, halten eine globale dict von functionhash: function

gfunctions = {} 

Während deserial Wenn Sie eine Funktion deserialisieren, fügen Sie sie zu gfunctions hinzu. Hier Artikel ist (Raute, Code, closurevalues, Modulname):

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

Und wenn man über den Sentinel-Wert für eine Funktion kommen, verwenden Sie den Proxy, vorbei in der Hash der Funktion:

lfunction = make_proxy(item[0]) 

Hier ist der Proxy. Es sieht die reale Funktion basierend auf dem Hash-up:

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

    return f_proxy 

Ich habe auch ein paar andere Änderungen vornehmen:

  • ich Gurke statt Marschall an einigen Stellen verwendet haben, könnten untersuchen Diese weitere
  • Ich bin der Modulname der Funktion in Serialisierung sowie Code und Schließung, so dass ich die richtigen Globals für die Funktion beim Deserialisieren nachschlagen kann.
  • Im Deserialisation, die Länge des Tupels sagen Ihnen, was Sie deserialising: 1 für einen einfachen Wert, 2 für eine Funktion, das Proxying, 4 für eine vollständig serialisierte Funktion neu

Hier ist die vollständige braucht Code.

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

Sie brauchen keine globale Suche, wenn Sie nur eine veränderbare nicht-lokale (zB eine 1-Element-Liste) innerhalb von 'make_proxy' machen, in die Sie die Funktion einfügen. Das sollte Lookup-Zeiten sparen, wenn es je darauf ankommt. Gutes Denken. – Veedrac

Verwandte Themen