修饰器中的修改功能 [英] Modify function in decorator

查看:215
本文介绍了修饰器中的修改功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

在思考过程中,我发现如果我能得到源代码的功能,我可以做到这一点。但是可以访问装饰器中的函数的源代码吗?如果我有这样一个装饰器:

  import inspect 

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

decorate
def test():
return 1

我得到一个OSError:

  OSError:无法获得源代码

这似乎是因为 test 在传递到 decorate 之前未完全形成。但是,这是有效的:

$ p $ import inspect

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

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

尽管如此,它并没有那个装饰风格。看起来这可能是可能的,因为定义了 f .__ code __




经过进一步的检查,看来只有当我将 inspect.getsource(f)放入 EXEC 。否则,我似乎可以得到源代码。






作为我脑海中第一件事的粗略草图,我正在考虑尾递归。我写了这个装饰器,不幸的是速度很慢,需要一个非常具体的写入函数风格来进行装饰:

  def tail_recurse(acc_default ):
def装饰(f):
def包装(* args,acc = acc_default):
args = args +(acc,)
,True:
return_type,* rargs = f(* args)
如果return_type是None:
返回rargs [-1]
args = rargs
返回包装
返回装饰

基本上,我正在考虑做一些简单的事情来替换函数的主体:

  True:
__body__
update_args

.wrapsrel =nofollow> functools.wraps 与您的原始代码:

 从functools导入检查
导入包装

@wraps
def装饰(f):
exec(inspect.getsource(f))
return eval(f .__ name__)

decorate
def test():
return 1
$ b $输出:

pre $ 在[2]中:test()$ b如果您打算在运行时更改源代码,那么您应该熟悉它。如果您计划在运行时更改源代码,那么您应该熟悉它与 ast 图书馆,有一个很好的来自pycon 2011的视频,Matthew Desmarais就如何演讲要使用ast模块将源代码从基本版本更改为更高级的选项,这是在演讲中使用的python to 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。访问(节点)
返回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)
$ b $ 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(} \\\


def visit_Eq(self,less):
self.src + ===
$ b $ def visit_Name(self,name):
self.src + ={}。format(name.id)
$ b $ def visit_BinOp(self,binop):
self.visit(binop.left)
self.src + =
self.visit(binop.op)
self.src + =
self.visit(binop.right)
$ b $ 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 +} \\\



def visit_Compare(self,comp):
self.visit(comp.left)
self.src + =
self.visit(comp.ops [0])
self.src + =
self.visit(comp.comparators [0])
$ b $ def visit_Call(self,call):
self.src + =
self.src + ={}(。format(call.func.id)
self.visit(call.args [0])
self.src + =)
$ b $ def visit_Add(self,add):
self.src + =+
$ b $ def visit_Sub(self,add):
self.src + = -
$ b $ def visit_Return(self,ret):
self.src + = self._indent(return)$​​ b $ b如果ret.value:
self.src + =
self.visit(ret.value)
self.src + =; \\\



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


检查导入源


def fibonacci(n):
如果n == 0:
返回0
如果n == 1:
return 1
return fibonacci(n - 1)+ fibonacci(n - 2)

运行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);
}

greentreesnakes 文档也值得一看。


I was thinking about making a decorator for the purpose of increasing performance. A decorator that modifies the source code of the function it decorates, and returns the modified function.

While thinking this through, I figured that if I could just get the source code of the function, I could do this. But is it possible to access the source code of a function inside a decorator? If I have a decorator like this:

import inspect

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

@decorate
def test():
    return 1

I get an OSError:

OSError: could not get source code

This appears to be because test is not fully formed before it is passed into decorate. However, this works:

import inspect

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

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

It just doesn't have that decorator flair to it, though. It seems that this might be possible, because f.__code__ is defined.


Upon further inspection, it appears that this only happens when I put the inspect.getsource(f) into exec. Otherwise, it seems that I can get the source code.


As a rough sketch of the first thing that's on my mind, I'm thinking of tail-recursion. I wrote this decorator that is unfortunately slow and requires a very specific style of writing the function to be decorated:

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

Basically, I'm thinking of doing something as simple as replacing the body of a function with:

while True:
    __body__
    update_args

解决方案

You can use functools.wraps with your original code:

import inspect
from functools import wraps

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

@decorate
def test():
    return 1

Output:

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

If you plan on changing source at runtime then you should get familiar with the ast library, there is an excellent video from pycon 2011 where Matthew Desmarais gives a talk on how to use the ast module to change the source code from the basics right up to more the more advanced options, this is a simple working example of the python to javascript translator that is used in the talk, it will work for simple examples like the fib function provided.

It should give you a good understanding of how the NodeTransformer works which is what you will want to use to manipulate your code at runtime, you can decorate your functions using something similar to the dec function below, the difference will be you will be returning compiled code:

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)

Running the dec function outputs our python as 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);
}

The greentreesnakes docs are also worth a read.

这篇关于修饰器中的修改功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆