Python: обезьяна исправляет исходный код функции - PullRequest
0 голосов
/ 16 декабря 2018

Могу ли я добавить префикс и суффикс к исходному коду функций?

Я знаю о декораторах и не хочу их использовать (в приведенном ниже минимальном примере не ясно, почему, но у меня есть мойпричины).

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 ]

0 голосов
/ 18 декабря 2018

Для вашей проблемы вам не нужно перекомпилировать ваши функции.Просто определите список функций, вы проверяете аргументы и возвращаете имя переменной:

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))
0 голосов
/ 16 декабря 2018

Вот примерная версия того, что можно сделать:

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), для возврата локальных (3) решений, основанных на входах ивыводит (как определено подсказками типа), в каком порядке должны выполняться части my_algorithm.Кроме того, возможность, которую я еще не реализовал, заключается в параллельном выполнении независимых частей (таких как SumFirstArguments и SinOfThird).Дайте мне знать, если вам нужен исходный код graphexecute, я не включил его здесь, потому что он содержит много вещей, которые не имеют отношения к этому вопросу.

...