Python:猴子修补函数的源代码

Ban*_*ach 6 python monkeypatching abstract-syntax-tree

我可以在函数的源代码中添加前缀和后缀吗?

我知道装饰器并且不想使用它们(下面的最小例子没有说明原因,但我有我的理由).

def f():
    print('world')
g = patched(f,prefix='print("Hello, ");',suffix='print("!");')
g() # Hello, world!
Run Code Online (Sandbox Code Playgroud)

这是我到目前为止:

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,如果我使用它然后g()如上所述调用就没有任何反应; 既不印刷world也不Hello, world!印刷.

Ban*_*ach 3

以下是可以执行的操作的粗略版本:

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!
Run Code Online (Sandbox Code Playgroud)

的调用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
Run Code Online (Sandbox Code Playgroud)

要点是,如果我重新调整 的部分,我会得到完全相同的输出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
Run Code Online (Sandbox Code Playgroud)

这是通过以下方式实现的:(1) 获取 的源my_algorithm并将其转换为 ast (2) 修补其中定义的每个函数my_algorithm(例如 SumFirstArguments)以返回局部变量 (3) 根据输入和输出进行决定(如类型提示所定义)的各部分应按什么顺序my_algorithm执行。此外,我还没有实现的一种可能性是并行执行独立部分(例如SumFirstArguments和SinOfThird)。如果您想要 的源代码graphexecute,请告诉我,我没有将其包含在此处,因为它包含很多与此问题无关的内容。

  • C 文化与 Python 文化是一个有趣的问题。我猜测——基于纯粹的Python视角——C程序历史上运行的限制,例如低内存和/或嵌入式系统、实时性等,使得探索创造性解决方案变得更加重要,甚至不惜付出代价的可读性。然而 Python 程序往往不会在这种受限的环境中运行,因此文化重视可读性,并且更倾向于说“仅仅因为你可以并不意味着你应该”。只是思考... (2认同)