代码之家  ›  专栏  ›  技术社区  ›  Bananach

python:monkey补丁函数的源代码

  •  6
  • Bananach  · 技术社区  · 6 年前

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

    我了解装饰师,不想使用它们(下面的最小示例不清楚原因,但我有我的理由)。

    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! 打印。

    2 回复  |  直到 6 年前
        1
  •  3
  •   Bananach    6 年前

    下面是可以做的事情的大致版本:

    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 来自if 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-算法 并将其转换为一个AST(2)修补中定义的每个函数 My-算法 (例如,sumfirstarguments)返回局部变量(3)根据输入和输出(由类型提示定义)决定 My-算法 应该执行。此外,我还没有实现的可能性是并行执行独立的部分(例如 SumFirstArguments SinOfThird )如果您需要的源代码,请通知我 graphexecute ,我没有把它包括在这里,因为它包含了很多与这个问题无关的东西。

        2
  •  1
  •   Daniel    6 年前

    对于您的问题,您不需要重新编译函数。只需定义函数列表,检查参数并返回变量名:

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