2009-08-14 8 views
2

Angenommen, ich möchte Code auszuführen, zum Beispielexec() Bytecode mit beliebigen Einträgen?

value += 5 

in einem Namensraum meiner eigenen (so das Ergebnis ist im Wesentlichen mydict['value'] += 5). Es gibt eine Funktion exec(), aber ich habe eine Zeichenfolge übergeben:

exec('value += 5', mydict) 

und Aussagen als Strings vorbei scheint seltsam (zum Beispiel ist es nicht koloriert, dass die Art und Weise).

def block(): 
     value += 5 

    ???(block, mydict) 

: Kann es wie getan werden? Der offensichtliche Kandidat für die letzte Zeile war exec(block.__code__, mydict), aber kein Glück: es wirft UnboundLocalError über value. Ich glaube, es führt im Wesentlichen block(), nicht der Code in Block, so dass Zuweisungen nicht einfach sind - ist das richtig?

Natürlich eine andere mögliche Lösung block.__code__ zerlegen würde ...

FYI, habe ich die Frage wegen this thread. Auch ist der Grund, warum einige (mich unentschlossen) Aufruf für neue Syntax

using mydict: 
     value += 5 

Beachten Sie, wie diese nicht Fehler wirft aber ändert sich nicht mydict entweder:

def block(value = 0): 
     value += 5 

    block(**mydict) 
+0

Was mit einfachen Klassendefinitionen falsch? –

+0

Ich bin mir nicht sicher, was Sie mit Klassen meinen. Ich werde versuchen, eine Antwort zu verfassen, die eine mögliche Idee aufzeigt, die du vielleicht meinst, aber fühle mich frei, mich zu korrigieren. –

Antwort

6

Sie können passieren Bytecode anstelle einer Zeichenfolge zu exec, müssen Sie nur das richtige Bytecode für den Zweck machen:

>>> bytecode = compile('value += 5', '<string>', 'exec') 
>>> mydict = {'value': 23} 
>>> exec(bytecode, mydict) 
>>> mydict['value'] 
28 

Insbesondere ...:

>>> import dis 
>>> dis.dis(bytecode) 
    1   0 LOAD_NAME    0 (value) 
       3 LOAD_CONST    0 (5) 
       6 INPLACE_ADD   
       7 STORE_NAME    0 (value) 
      10 LOAD_CONST    1 (None) 
      13 RETURN_VALUE   

die Lade- und Speicherbefehle der _NAME Überzeugung sein muss, und diese compile macht sie so, während ...:

>>> def f(): value += 5 
... 
>>> dis.dis(f.func_code) 
    1   0 LOAD_FAST    0 (value) 
       3 LOAD_CONST    1 (5) 
       6 INPLACE_ADD   
       7 STORE_FAST    0 (value) 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE   

... Code in Eine Funktion ist für die Verwendung der _FAST-Versionen optimiert, und diese funktionieren nicht für ein an exec übergebenes Diktat. Wenn Sie irgendwie mit einem Bytecode mit den _FAST-Anweisungen begonnen haben, könnten Sie stattdessen einen Patch verwenden, um stattdessen den _NAME-Typ zu verwenden, z. mit bytecodehacks oder einem ähnlichen Ansatz.

+0

Es sieht so aus, als ob das hacken des Bytecodes in diesem Fall eine gute Idee ist, zumal die Semantik der gewünschten Funktion "apply_code_to_dict" einfach ist. –

3

Verwenden Sie das global Schlüsselwort dynamische Scoping zu zwingen, auf alle Variablen, die Sie von innerhalb des Blocks ändern möchten:

def block(): 
    global value 
    value += 5 

mydict = {"value": 42} 
exec(block.__code__, mydict) 
print(mydict["value"]) 
0

Von S.Lott Kommentar oben Ich glaube, ich die Idee für eine Antwort bekommen mit Schaffung neuer Klasse.

class _(__metaclass__ = change(mydict)): 
    value += 1 
    ... 

wo change ist ein Metaklasse deren __prepare__ liest Wörterbuch und deren __new__ Updates Wörterbuch.

für die Wiederverwendung, würde das Snippet unten arbeiten, aber es ist irgendwie hässlich:

def increase_value(d): 
    class _(__metaclass__ = change(d)): 
     value += 1 
     ... 

increase_value(mydict) 
3

Hier ein verrücktes Dekorateur ist so, um einen Block zu erstellen, die „custom locals“ verwendet. In Wirklichkeit ist es ein schneller Hack, alle Variablenzugriffe innerhalb der Funktion auf globalen Zugriff umzustellen und das Ergebnis mit dem benutzerdefinierten Einheimischwörterbuch als Umgebung auszuwerten.

import dis 
import functools 
import types 
import string 

def withlocals(func): 
    """Decorator for executing a block with custom "local" variables. 

    The decorated function takes one argument: its scope dictionary. 

    >>> @withlocals 
    ... def block(): 
    ...  counter += 1 
    ...  luckynumber = 88 

    >>> d = {"counter": 1} 
    >>> block(d) 
    >>> d["counter"] 
    2 
    >>> d["luckynumber"] 
    88 
    """ 
    def opstr(*opnames): 
     return "".join([chr(dis.opmap[N]) for N in opnames]) 

    translation_table = string.maketrans(
      opstr("LOAD_FAST", "STORE_FAST"), 
      opstr("LOAD_GLOBAL", "STORE_GLOBAL")) 

    c = func.func_code 
    newcode = types.CodeType(c.co_argcount, 
          0, # co_nlocals 
          c.co_stacksize, 
          c.co_flags, 
          c.co_code.translate(translation_table), 
          c.co_consts, 
          c.co_varnames, # co_names, name of global vars 
          (), # co_varnames 
          c.co_filename, 
          c.co_name, 
          c.co_firstlineno, 
          c.co_lnotab) 

    @functools.wraps(func) 
    def wrapper(mylocals): 
     return eval(newcode, mylocals) 
    return wrapper 

if __name__ == '__main__': 
    import doctest 
    doctest.testmod() 

Dies ist nur ein Affe-Patchen Anpassung von brillantem Rezepte jemand für ein goto decorator

+0

eher als ein Dekorator, könnte mehr vernünftige Verwendung machen es nur einen Funktionsaufruf 'withlocals (Block, d)' – u0b34a0f6ae

+0

Wow, das ist schön. –

+0

danke. Ich vermute, Martelli weiß viel mehr darüber als ich, aber ich habe es geschafft, etwas zusammen zu schmeißen, das "funktioniert" (schämt mich, es funktionierend zu nennen). Vielleicht ist "LOAD_NAME" besser als "LOAD_GLOBAL"? – u0b34a0f6ae