Python: 猴子修补一个函数的源代码
Python: monkey patch a function's source code
函数源码可以加前缀和后缀吗?
我了解装饰器但不想使用它们(下面的最小示例没有说明原因,但我有我的理由)。
def f():
print('world')
g = patched(f,prefix='print("Hello, ");',suffix='print("!");')
g() # Hello, world!
这是我目前的情况:
import inspect
import ast
import copy
def patched(f,prefix,suffix):
source = inspect.getsource(f)
tree = ast.parse(source)
new_body = [
ast.parse(prefix).body[0],
*tree.body[0].body,
ast.parse(suffix).body[0]
]
tree.body[0].body = new_body
g = copy.deepcopy(f)
g.__code__ = compile(tree,g.__code__.co_filename,'exec')
return g
不幸的是,如果我使用它然后像上面那样调用 g()
,什么也不会发生; world
和 Hello, world!
均未打印。
以下是可以完成的粗略版本:
import inspect
import ast
import copy
def patched(f,prefix,suffix):
source = inspect.getsource(f)
tree = ast.parse(source)
new_body = [
ast.parse(prefix).body[0],
*tree.body[0].body,
ast.parse(suffix).body[0]
]
tree.body[0].body = new_body
code = compile(tree,filename=f.__code__.co_filename,mode='exec')
namespace = {}
exec(code,namespace)
g = namespace[f.__name__]
return g
def temp():
pass
def f():
print('world',end='')
g = patched(f,prefix='print("Hello, ",end="")',suffix='print("!",end="")')
g() # Hello, world!
compile
的调用编译了整个模块(用tree
表示)。然后这个模块在一个空的命名空间中执行,最终从中提取所需的功能。 (警告:如果 f
使用这些名称空间,则需要用 f
来自的一些全局变量填充。)
经过更多的工作,这里是一个真实的例子,说明可以用它做什么。它使用了上述原理的一些扩展版本:
import numpy as np
from playground import graphexecute
@graphexecute(verbose=True)
def my_algorithm(x,y,z):
def SumFirstArguments(x,y)->sumxy:
sumxy = x+y
def SinOfThird(z)->sinz:
sinz = np.sin(z)
def FinalProduct(sumxy,sinz)->prod:
prod = sumxy*sinz
def Return(prod):
return prod
print(my_algorithm(x=1,y=2,z=3))
#OUTPUT:
#>>Executing part SumFirstArguments
#>>Executing part SinOfThird
#>>Executing part FinalProduct
#>>Executing part Return
#>>0.4233600241796016
关键是,如果我重新调整 my_algorithm
的部分,我会得到完全相同的输出,例如:
@graphexecute(verbose=True)
def my_algorithm2(x,y,z):
def FinalProduct(sumxy,sinz)->prod:
prod = sumxy*sinz
def SumFirstArguments(x,y)->sumxy:
sumxy = x+y
def SinOfThird(z)->sinz:
sinz = np.sin(z)
def Return(prod):
return prod
print(my_algorithm2(x=1,y=2,z=3))
#OUTPUT:
#>>Executing part SumFirstArguments
#>>Executing part SinOfThird
#>>Executing part FinalProduct
#>>Executing part Return
#>>0.4233600241796016
这通过 (1) 获取 my_algorithm
的源并将其转换为 ast (2) 将 my_algorithm
中定义的每个函数(例如 SumFirstArguments)修补到 return 局部变量来实现(3) 根据输入和输出(由类型提示定义)决定 my_algorithm
的部分应该执行的顺序。此外,我还没有实现的一种可能性是并行执行独立的部分(例如 SumFirstArguments
和 SinOfThird
)。如果你想要 graphexecute
的源代码,请告诉我,我没有把它包含在这里,因为它包含很多与这个问题无关的东西。
对于你的问题,你不需要重新编译你的函数。只需定义一个函数列表,检查参数和 return 变量名:
def FinalProduct(sumxy, sinz) -> "prod":
return sumxy * sinz
def SumFirstArguments(x, y) -> "sumxy":
return x + y
def SinOfThird(z) -> "sinz":
return np.sin(z)
def execute(funcs, **args):
result = None
while funcs:
func = funcs.pop(0)
try:
kw = {a: args[a]
for a in func.__code__.co_varnames[:func.__code__.co_argcount]
}
except KeyError:
# not all arguments found
funcs.append(func)
else:
print(func,kw)
result = func(**kw)
args[func.__annotations__['return']] = result
return result
print(execute([FinalProduct, SumFirstArguments, SinOfThird], x=1,y=2,z=3))
函数源码可以加前缀和后缀吗?
我了解装饰器但不想使用它们(下面的最小示例没有说明原因,但我有我的理由)。
def f():
print('world')
g = patched(f,prefix='print("Hello, ");',suffix='print("!");')
g() # Hello, world!
这是我目前的情况:
import inspect
import ast
import copy
def patched(f,prefix,suffix):
source = inspect.getsource(f)
tree = ast.parse(source)
new_body = [
ast.parse(prefix).body[0],
*tree.body[0].body,
ast.parse(suffix).body[0]
]
tree.body[0].body = new_body
g = copy.deepcopy(f)
g.__code__ = compile(tree,g.__code__.co_filename,'exec')
return g
不幸的是,如果我使用它然后像上面那样调用 g()
,什么也不会发生; world
和 Hello, world!
均未打印。
以下是可以完成的粗略版本:
import inspect
import ast
import copy
def patched(f,prefix,suffix):
source = inspect.getsource(f)
tree = ast.parse(source)
new_body = [
ast.parse(prefix).body[0],
*tree.body[0].body,
ast.parse(suffix).body[0]
]
tree.body[0].body = new_body
code = compile(tree,filename=f.__code__.co_filename,mode='exec')
namespace = {}
exec(code,namespace)
g = namespace[f.__name__]
return g
def temp():
pass
def f():
print('world',end='')
g = patched(f,prefix='print("Hello, ",end="")',suffix='print("!",end="")')
g() # Hello, world!
compile
的调用编译了整个模块(用tree
表示)。然后这个模块在一个空的命名空间中执行,最终从中提取所需的功能。 (警告:如果 f
使用这些名称空间,则需要用 f
来自的一些全局变量填充。)
经过更多的工作,这里是一个真实的例子,说明可以用它做什么。它使用了上述原理的一些扩展版本:
import numpy as np
from playground import graphexecute
@graphexecute(verbose=True)
def my_algorithm(x,y,z):
def SumFirstArguments(x,y)->sumxy:
sumxy = x+y
def SinOfThird(z)->sinz:
sinz = np.sin(z)
def FinalProduct(sumxy,sinz)->prod:
prod = sumxy*sinz
def Return(prod):
return prod
print(my_algorithm(x=1,y=2,z=3))
#OUTPUT:
#>>Executing part SumFirstArguments
#>>Executing part SinOfThird
#>>Executing part FinalProduct
#>>Executing part Return
#>>0.4233600241796016
关键是,如果我重新调整 my_algorithm
的部分,我会得到完全相同的输出,例如:
@graphexecute(verbose=True)
def my_algorithm2(x,y,z):
def FinalProduct(sumxy,sinz)->prod:
prod = sumxy*sinz
def SumFirstArguments(x,y)->sumxy:
sumxy = x+y
def SinOfThird(z)->sinz:
sinz = np.sin(z)
def Return(prod):
return prod
print(my_algorithm2(x=1,y=2,z=3))
#OUTPUT:
#>>Executing part SumFirstArguments
#>>Executing part SinOfThird
#>>Executing part FinalProduct
#>>Executing part Return
#>>0.4233600241796016
这通过 (1) 获取 my_algorithm
的源并将其转换为 ast (2) 将 my_algorithm
中定义的每个函数(例如 SumFirstArguments)修补到 return 局部变量来实现(3) 根据输入和输出(由类型提示定义)决定 my_algorithm
的部分应该执行的顺序。此外,我还没有实现的一种可能性是并行执行独立的部分(例如 SumFirstArguments
和 SinOfThird
)。如果你想要 graphexecute
的源代码,请告诉我,我没有把它包含在这里,因为它包含很多与这个问题无关的东西。
对于你的问题,你不需要重新编译你的函数。只需定义一个函数列表,检查参数和 return 变量名:
def FinalProduct(sumxy, sinz) -> "prod":
return sumxy * sinz
def SumFirstArguments(x, y) -> "sumxy":
return x + y
def SinOfThird(z) -> "sinz":
return np.sin(z)
def execute(funcs, **args):
result = None
while funcs:
func = funcs.pop(0)
try:
kw = {a: args[a]
for a in func.__code__.co_varnames[:func.__code__.co_argcount]
}
except KeyError:
# not all arguments found
funcs.append(func)
else:
print(func,kw)
result = func(**kw)
args[func.__annotations__['return']] = result
return result
print(execute([FinalProduct, SumFirstArguments, SinOfThird], x=1,y=2,z=3))