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!"))