Получить строку вызова для функции Python, набранную в командной строке - PullRequest
2 голосов
/ 06 мая 2019

Я хотел бы сохранить точную строку, которая была набрана для вызова функции, из самой функции с помощью самоанализа (я не могу / не хочу взламывать интерпретатор командной строки - например, получение истории из readline или что не то, что я ищу).

Допустим, если пользователь набрал:

>>> myfunc('a', 'b', 1, mykwarg='hello')

Я хотел бы получить строку вызова (т.е. myfunc('a', 'b', 1, mykwarg='hello')) из кода внутри myfunc.

Я могу создать что-то вроде этого:

def myfunc(a,b,c,mykwarg=None):
    frame = inspect.currentframe()
    sig = inspect.signature(myfunc)
    args = []
    for param in sig.parameters.values():
        if param.name in frame.f_locals:
                args.append(f"{param.name}={str(frame.f_locals[param.name])}")
    cmd = f"{frame.f_code.co_name}({','.join(args)})"

    print(cmd)

Я получаю:

>>> myfunc('a', 'b', 1, mykwarg='hello')
myfunc(a=a,b=b,c=1,mykwarg=hello)

Что не совсем то, что набрал пользователь. Кроме того, я надеюсь, что есть что-то еще надежный и менее "хакерский", чтобы попробовать ...

Вариант использования : я хочу иметь возможность связать командный вызов из моей библиотеки с его результатом. Я не хочу жестко программировать хранилище вызовов команд для каждой функции, я бы предпочел использовать декоратор или что-то в этом роде. Вероятно, это гораздо проще сделать из REPL, но я бы не хотел зависеть от этого (например, если пользователь вызывает функцию из своей собственной программы, он все равно должен иметь возможность связать вызов команды с результатом).

1 Ответ

0 голосов
/ 06 мая 2019

Наконец-то я отвечаю на свой вопрос, надеюсь, однажды он может кому-нибудь помочь.

Я решил попробовать пойти по пути dis, т.е. «дизассемблирование» объекта кода Python внешний фрейм, вызывающий мою функцию, чтобы увидеть, как она была вызвана, чтобы иметь возможность восстановить командную строку:

import dis
import inspect
import io
import ast
import re

def get_function_call_string():
    in_func = False
    args=[]
    kwargs=[]
    func_name = inspect.stack()[1][3]
    frame = inspect.currentframe().f_back.f_back
    dis_output = io.StringIO()
    dis.dis(frame.f_code, file=dis_output)
    for line in dis_output.getvalue().split('\n'):
        instr = re.findall(r'^.*\s([A-Z_]+)\s*', line)[0]
        if instr.startswith("CALL_FUNCTION") and in_func:
           break
        elif instr.startswith('LOAD_'):
            name = re.findall(r'^.*\s\((.+)\)$', line)[0]
            if in_func:
                if name.startswith('('):
                    kwargs = ast.literal_eval(name)
                else:
                    args.append(name)
            elif name == func_name:
                in_func = True
    kwargs = [f"{a}={args.pop()}" for a in kwargs]
    return f"{func_name}({', '.join(args)}{', ' if kwargs else ''}{', '.join(kwargs)})"

Пример:

>>> def f(a,b,arg="toto"):
        print(get_function_call_string())
>>> f(1,"test","example")
f(1, 'test', 'example')
>>> f(1, "test", arg="hello")
(1, 'test', arg='hello')

Это не полный ответ, поскольку он не может обрабатывать некоторые типы аргументов, такие как дикт или список ... Я, возможно, продолжу в этом или передумаю и сделаю по-другому.

...