簡體   English   中英

在裝飾器中修改功能

[英]Modify function in decorator

我正在考慮為提高性能而制作一個裝飾器。 一個裝飾器,修改它裝飾的函數的源代碼,並返回修改后的函數。

在考慮這一點時,我想如果我能得到函數的源代碼,我就可以做到這一點。 但是可以在裝飾器內訪問函數的源代碼嗎? 如果我有這樣的裝飾者:

import inspect

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

@decorate
def test():
    return 1

我得到一個OSError:

OSError: could not get source code

這似乎是因為test在進入decorate之前沒有完全形成。 但是,這有效:

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 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

基本上,我正在考慮做一些簡單的事情,比如用以下函數替換函數體:

while True:
    __body__
    update_args

您可以將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

輸出:

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

如果您計划在運行時更改源代碼,那么您應該熟悉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)

運行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文檔也值得一讀。

這有效:

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

我認為問題是在函數源中包含裝飾器。

# 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看到@decorate用於在字符串中定義的test ,因此它decorate遞歸方式調用decorate ,但這次inspect.getsource失敗,因為它無法找到字符串中定義的函數的源。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM