Python 使用decorator更改函数代码并使用eval执行?

Python 使用decorator更改函数代码并使用eval执行?,python,eval,decorator,Python,Eval,Decorator,我试图应用一个修改函数代码的装饰器,然后用修改过的代码执行这个函数 下面是带有示例功能的temp模块。我只想让函数返回[*args,*kwargs.items(),123]而不是[*args,*kwargs.items()]如果某个装饰程序应用于此函数 from inspect import getsource def some_decorator(method): def wrapper(*args, **kwargs): source_code = getsourc

我试图应用一个修改函数代码的装饰器,然后用修改过的代码执行这个函数

下面是带有示例功能的
temp
模块。我只想让函数返回
[*args,*kwargs.items(),123]
而不是
[*args,*kwargs.items()]
如果
某个装饰程序应用于此函数

from inspect import getsource

def some_decorator(method):
    def wrapper(*args, **kwargs):
        source_code = getsource(method)
        code_starts_at = source_code.find('):') + 2
        head = source_code[:code_starts_at]
        body = source_code[code_starts_at:]

        lines = body.split('\n')
        return_line = [i for i in lines if 'return' in i][0]
        old_expr = return_line.replace('    return ', '')
        new_expr = old_expr.replace(']', ', 123]')

        new_expr = head + '\n' + '    return ' + new_expr

        return eval(new_expr)
    return wrapper

@some_decorator
def example_func(*args, *kwargs):
    return [*args, *kwargs]
编辑:请注意,这只是一个玩具示例,我不打算将新值附加到列表中,而是重写函数的一大块

from inspect import getsource

def some_decorator(method):
    def wrapper(*args, **kwargs):
        source_code = getsource(method)
        code_starts_at = source_code.find('):') + 2
        head = source_code[:code_starts_at]
        body = source_code[code_starts_at:]

        lines = body.split('\n')
        return_line = [i for i in lines if 'return' in i][0]
        old_expr = return_line.replace('    return ', '')
        new_expr = old_expr.replace(']', ', 123]')

        new_expr = head + '\n' + '    return ' + new_expr

        return eval(new_expr)
    return wrapper

@some_decorator
def example_func(*args, *kwargs):
    return [*args, *kwargs]
再解释一下:我正在重写原始函数

def example_func(*args, **kwargs):
    return [*args, *kwargs.items()]

我希望
eval
能够编译并运行这个修改后的函数

from inspect import getsource

def some_decorator(method):
    def wrapper(*args, **kwargs):
        source_code = getsource(method)
        code_starts_at = source_code.find('):') + 2
        head = source_code[:code_starts_at]
        body = source_code[code_starts_at:]

        lines = body.split('\n')
        return_line = [i for i in lines if 'return' in i][0]
        old_expr = return_line.replace('    return ', '')
        new_expr = old_expr.replace(']', ', 123]')

        new_expr = head + '\n' + '    return ' + new_expr

        return eval(new_expr)
    return wrapper

@some_decorator
def example_func(*args, *kwargs):
    return [*args, *kwargs]
当我尝试运行它时,它返回一个语法错误

from temp import example_func
example_func(5)
我知道
eval
能够处理以下问题:

[*args, *kwargs.items(), 123]
但仅当已声明了
args
kwargs
时。我希望在执行
example\u func
时从
example\u func(args,kwargs)
读取它们

我假设只需将修改后的函数代码写入一个文件

def example_func(*args, **kwargs):
    return [*args, *kwargs.items(), 123]
一些_decorator
用修改过的代码而不是原来的代码来执行函数,效果会很好。但是,理想情况下,我会跳过创建任何中间文件


有可能做到这一点吗?

虽然从技术上讲,您可以用Python中的函数和装饰器做任何事情,但您不应该这样做

在这种特定情况下,向返回列表的函数添加额外值非常简单:

def some_decorator(method):
    def wrapper(*args, **kwargs):
        result = method(*args, **kwargs)
        return result + [123]
    return wrapper
这不需要任何函数代码重写。如果您所做的只是更改函数的输入或输出,那么只需更改输入或输出,并保留函数本身

装饰器在这里主要是语法上的糖分,一种改变的方式

def function_name(*args, **kwargs):
    # ...

function_name = create_a_wrapper_for(function_name)
进入

还请注意,无法更改您的功能,因为
eval()
严格限制为。创建函数的
def
语法是。基本上,语句可以包含表达式和其他语句(例如,
if:
),但表达式不能包含语句。这就是为什么会出现
SyntaxError
异常;
[*args,*kwargs.items()]
是一个有效的表达式,
return[*args,*kwargs.items()]
是一个语句(包含表达式):

那么就不能单独执行
example()
函数;它是模块全局名称空间的一部分,在调用函数时在该名称空间中查找
extra_value
。函数具有对创建它们的模块的全局名称空间的引用,可通过
函数访问。使用
exec()
执行创建函数的
def
语句时,新函数对象将连接到传入的全局命名空间。请注意,
def
创建一个函数对象并将其分配给函数名,因此您必须再次从同一名称空间检索该对象:

>>> namespace = {}
>>> exec("def foo(): return 42", namespace)
>>> namespace["foo"]
<function foo at 0x7f8194fb1598>
>>> namespace["foo"]()
42
>>> namespace["foo"].__globals__ is namespace
True
因为现在
return
进一步缩进,所以列表中的字符串值中有
[…]
括号,列表的结束
]
括号完全位于单独的一行

让Python将源代码编译成一个抽象语法树(通过),然后处理该树,这样做会更好。定义良好的对象的有向图比文本更容易操作(文本在使用空格等方面更灵活)。上面的代码和您的示例都会生成一个带有
Return()
节点的树,该节点包含一个顶级为
List()
节点的表达式。您可以遍历该树,找到所有
Return()
节点,并更改它们的
List()
节点,在列表内容的末尾添加一个额外的节点

Python AST可以编译为代码对象(使用),然后通过
exec()
(它不仅接受文本,还接受代码对象)运行

有关重写Python代码的实际项目示例,请参见。他们使用模块导入钩子来实现这一点,但只要函数的源代码可用,您也可以使用装饰器来实现

下面是一个使用
ast
模块更改
return
语句中的列表,并添加任意常量的示例:

导入ast、检查、功能工具
类ReturnListInsertion(ast.NodeTransformer):
定义初始化(自、值到插入):
self.value=插入值
def访问功能def(自身、节点):
#从AST中删除'some_decorator'decorator
#我们不需要继续应用它。
如果node.decorator\u列表:
node.decorator\u列表=[
n代表node.decorator\u列表中的n
如果不是(isinstance(n,ast.Name)和n.id='some_decorator')
]
返回self.generic_访问(节点)
def访问返回(自身、节点):
如果isinstance(node.value,ast.List):
#Python3.8及以上版本改为使用ast.Constant,这比
#灵活。
node.value.elts.append(ast.Num(self.value))
返回self.generic_访问(节点)
def some_decorator(方法):
source\u code=inspect.getsource(方法)
tree=ast.parse(源代码)
更新=返回列表插入(123)。访问(树)
#修复所有行号参考,使其与原始行号匹配
更新=ast.increment\u lineno(
修复缺失位置(更新),
方法。\代码\公司\一线号
)
ast.copy_位置(更新的.body[0],树)
#再次编译,作为一个模块,然后执行编译后的字节码和
#提取新的函数对象。使用原始名称空间
#使函数中的所有全局引用仍然有效。
代码=编译(树,inspect.getfile(方法),'exec')
名称空间=方法__
执行主任(代码,nam)
def example(*args, **kwargs):
    return [extra_value(), *args, *kwargs.items()]

def extra_value():
    return 42
>>> namespace = {}
>>> exec("def foo(): return 42", namespace)
>>> namespace["foo"]
<function foo at 0x7f8194fb1598>
>>> namespace["foo"]()
42
>>> namespace["foo"].__globals__ is namespace
True
def example(*args, **kwargs):
    if args or kwargs:
        return [
            "[called with arguments:]",
            *args,
            *kwargs.items()
        ]
@some_decorator
def example(*args, **kwargs):
    return [extra_value(), *args, *kwargs.items()]

def extra_value():
    return 42

if __name__ == '__main__':
    print(example("Monty", "Python's", name="Flying circus!"))