2015-06-26 10 views
10

Ich habe überlegt, einen Dekorateur zu machen, um die Leistung zu erhöhen. Ein Decorator, der den Quellcode der Funktion, die er dekoriert, ändert und die geänderte Funktion zurückgibt.Funktion im Dekorateur ändern

Während ich darüber nachdachte, dachte ich, wenn ich nur den Quellcode der Funktion bekommen könnte, könnte ich das machen. Aber ist es möglich, innerhalb eines Decorators auf den Quellcode einer Funktion zuzugreifen? Wenn ich einen Dekorateur wie diese:

import inspect 

def decorate(f): 
    exec(inspect.getsource(f)) 
    return eval(f.__name__) 

@decorate 
def test(): 
    return 1 

Ich erhalte eine OSError:

OSError: could not get source code 

Dies scheint zu sein, weil test nicht vollständig ausgebildet, bevor sie in decorate geleitet wird. Dies funktioniert jedoch:

import inspect 

def decorate(f): 
    exec(inspect.getsource(f)) 
    return eval(f.__name__) 

def test(): 
    return 1 
test = decorate(test) 

Es hat einfach nicht, dass Dekorateur Flair, obwohl. Es scheint, dass dies möglich ist, weil f.__code__ definiert ist.


Bei einer weiteren Untersuchung scheint es, dass dies nur geschieht, wenn ich die inspect.getsource(f) in exec setzen. Ansonsten scheint es, dass ich den Quellcode bekommen kann.


Als eine grobe Skizze der ersten Sache, die in meinem Kopf ist, denke ich an Tail-Rekursion. Ich schrieb dieses Dekorateur, die leider langsam und erfordert eine sehr spezielle Art von der Funktion Schreiben dekoriert werden:

def tail_recurse(acc_default): 
    def decorate(f): 
     def wrapper(*args, acc=acc_default): 
      args = args + (acc,) 
      while True: 
       return_type, *rargs = f(*args) 
       if return_type is None: 
        return rargs[-1] 
       args = rargs 
     return wrapper 
    return decorate 

Grundsätzlich denke ich an mit etwas so einfaches wie Ersetzen der Körper einer Funktion zu tun:

while True: 
    __body__ 
    update_args 
+3

ast ist wahrscheinlich besser geeignet für das, was Sie wollen –

+0

Lassen Sie eine Zeile leer vor 'exec (inspect.getsource (f))' und es wird funktionieren. Interessant!! –

+1

@AshwiniChaudhary Das tut nichts für mich .... Aber dies führt mich zu der Annahme, dass dies ein Problem mit der Parallelität sein könnte, oder Implementierung abhängig – Justin

Antwort

4

Sie können functools.wraps mit Ihrem ursprünglichen Code verwenden:

import inspect 
from functools import wraps 

@wraps 
def decorate(f): 
    exec(inspect.getsource(f)) 
    return eval(f.__name__) 

@decorate 
def test(): 
    return 1 

Ausgang:

In [2]: test() 
Out[2]: 1 

Wenn Sie auf Ändern Quelle zur Laufzeit zu planen, dann sollten Sie mit der ast Bibliothek vertraut, gibt es eine ausgezeichnet video from pycon 2011 wo Matthew Desmarais einen Vortrag darüber gibt, wie man das ast-Modul verwendet, um den Quellcode von den Grundlagen bis zu den fortgeschritteneren Optionen zu ändern, ist dies ein einfaches Arbeitsbeispiel für den Python-zu-Javascript-Übersetzer t Hut wird in der Diskussion verwendet, es wird für einfache Beispiele wie die Fib-Funktion funktionieren.

Es sollte Ihnen ein gutes Verständnis davon geben, wie der NodeTransformer funktioniert, was Sie verwenden werden, um Ihren Code zur Laufzeit zu manipulieren, können Sie Ihre Funktionen mit etwas ähnlich der Dec-Funktion unten, der Unterschied wird Sie sein kompilierte Code wird zurückkommen:

from ast import parse, NodeTransformer 


class Transformer(NodeTransformer): 
    def __init__(self): 
     self.src = "" 
     self.indent = 0 

    def translate(self, node): 
     self.visit(node) 
     return self.src 

    def _indent(self, line): 
     return "{}{line}".format(" " * self.indent, line=line) 

    def render(self, body): 
     self.indent += 2 
     for stmt in body: 
      self.visit(stmt) 
     self.indent -= 2 

    def visit_Num(self, node): 
     self.src += "{}".format(node.n) 

    def visit_Str(self, node): 
     self.src += "{}".format(node.s) 

    def visit_FunctionDef(self, defn): 
     args = ",".join(name.arg for name in defn.args.args) 
     js_defn = "var {} = function({}){{\n" 
     self.src += self._indent(js_defn.format(defn.name, args)) 
     self.render(defn.body) 
     self.src += self._indent("}\n") 

    def visit_Eq(self, less): 
     self.src += "==" 

    def visit_Name(self, name): 
     self.src += "{}".format(name.id) 

    def visit_BinOp(self, binop): 
     self.visit(binop.left) 
     self.src += " " 
     self.visit(binop.op) 
     self.src += " " 
     self.visit(binop.right) 

    def visit_If(self, _if): 
     self.src += self._indent("if (") 
     self.visit(_if.test) 
     self.src += ") {\n" 
     self.render(_if.body) 
      self.src += " "*self.indent + "}\n" 


    def visit_Compare(self, comp): 
     self.visit(comp.left) 
     self.src += " " 
     self.visit(comp.ops[0]) 
     self.src += " " 
     self.visit(comp.comparators[0]) 

    def visit_Call(self, call): 
     self.src += " " 
     self.src += "{}(".format(call.func.id) 
     self.visit(call.args[0]) 
     self.src += ")" 

    def visit_Add(self, add): 
     self.src += "+" 

    def visit_Sub(self, add): 
     self.src += "-" 

    def visit_Return(self, ret): 
     self.src += self._indent("return") 
     if ret.value: 
      self.src += " " 
      self.visit(ret.value) 
     self.src += ";\n" 


def dec(f): 
    source = getsource(f) 
    _ast = parse(source) 
    trans = Transformer() 
    trans.indent = 0 
    return trans.translate(_ast) 


from inspect import getsource 


def fibonacci(n): 
    if n == 0: 
     return 0 
    if n == 1: 
     return 1 
    return fibonacci(n - 1) + fibonacci(n - 2) 

Ausführen der Dezember-Funktion gibt unseren python als javascript:

print(dec(fibonacci)) 
var fibonacci = function(n){ 
    if (n == 0) { 
    return 0; 
    } 
    if (n == 1) { 
    return 1; 
    } 
    return fibonacci(n - 1) + fibonacci(n - 2); 
} 

die greentreesnakes docs sind auch lesenswert.

1

Dies funktioniert:

import inspect, itertools 

def decorate(f): 
    source = itertools.dropwhile(lambda line: line.startswith('@'), inspect.getsource(f).splitlines()) 
    exec('\n'.join(source)) 
    return eval(f.__name__) 

@decorate 
def test(): 
    return 1 

ich denke, das Problem ist die Einbeziehung des Dekorateur in der Funktion Quelle ist.

# foo.py 
import inspect 

def decorate(f): 
    print inspect.getsource(f) 

@decorate 
def test(): 
    return 1 

>>> import foo 
@decorate 
def test(): 
    return 1 
>>> # Notice the decorator is included in the source. 

exec sieht @decorate für eine test in einem String definiert, so dass es decorate rekursiv aufruft, aber diesmal inspect.getsource versagt, weil sie nicht die Quelle einer Funktion finden können definiert in ein Faden.

+0

Ahh, das würde Sinn machen – Justin

+0

Das ist schlau. Es sieht so aus, als würde 'exec' den Dekorateur sehen und rekursiv anwenden, was schief geht. – Justin