在装饰器中修改功能

Jus*_*tin 13 python function decorator python-decorators

我正在考虑为提高性能而制作一个装饰器.一个装饰器,修改它装饰的函数的源代码,并返回修改后的函数.

在考虑这一点时,我想如果我能得到函数的源代码,我就可以做到这一点.但是可以在装饰器内访问函数的源代码吗?如果我有这样的装饰者:

import inspect

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

@decorate
def test():
    return 1
Run Code Online (Sandbox Code Playgroud)

我得到一个OSError:

OSError: could not get source code
Run Code Online (Sandbox Code Playgroud)

这似乎是因为test在传入之前没有完全形成decorate.但是,这有效:

import inspect

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

def test():
    return 1
test = decorate(test)
Run Code Online (Sandbox Code Playgroud)

然而,它并没有那种装饰风格.似乎这可能是可能的,因为f.__code__ 定义.


经过进一步检查,似乎只有当我inspect.getsource(f)进入时才会发生这种情况exec.否则,似乎我可以获得源代码.


作为我脑海中第一件事的粗略草图,我正在考虑尾部递归.我编写的这个装饰器很慢,并且需要一种非常具体的写入要装饰的函数的样式:

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
Run Code Online (Sandbox Code Playgroud)

基本上,我正在考虑做一些简单的事情,比如用以下函数替换函数体:

while True:
    __body__
    update_args
Run Code Online (Sandbox Code Playgroud)

Pad*_*ham 6

您可以将functools.wraps与您的原始代码一起使用:

import inspect
from functools import wraps

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

@decorate
def test():
    return 1
Run Code Online (Sandbox Code Playgroud)

输出:

In [2]: test()
Out[2]: 1
Run Code Online (Sandbox Code Playgroud)

如果您计划在运行时更改源代码,那么您应该熟悉ast库,pycon 2011中有一段精彩的视频,其中 Matthew Desmarais 介绍了如何使用 ast 模块将源代码从基础更改为更多更高级的选项,这是在演讲中使用的python到javascript翻译器的简单工作示例,它将适用于提供的fib函数等简单示例。

它应该让您很好地了解 NodeTransformer 的工作原理,这是您在运行时想要用来操作代码的方式,您可以使用类似于下面的 dec 函数的东西来装饰您的函数,不同之处在于您将返回编译代码:

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)
Run Code Online (Sandbox Code Playgroud)

运行 dec 函数将我们的 python 输出为 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);
}
Run Code Online (Sandbox Code Playgroud)

greentreesnakes文档也值得一读。