3

I would like to store the exact string that was typed for a function call, from the function itself using introspection (I cannot/don't want to hack the command line interpreter -- so, for example getting history from readline or whatever is not what I am looking for).

Let's say, if user typed:

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

I would like to get the call string (i.e. myfunc('a', 'b', 1, mykwarg='hello')) from the code inside myfunc.

I can craft something like this:

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)

I get:

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

Which is not exactly what user typed. Also, I hope there is something more robust and less 'hackish' to try...

Use case: I want to be able to associate a command call from my library with its result. I do not want to hard-code the command call storage for each function, I would prefer to use a decorator or something like that. This is probably much easier to do from the REPL, but I would like to not depend on it (like, if user calls the function from its own program, it should still be able to associate the command call with the result).

9
  • Look for answers about the Python REPL. Commented May 6, 2019 at 10:13
  • @smci thanks but I do want an answer too much related to the Python REPL (see my edit) Commented May 6, 2019 at 10:14
  • Also, can you help us understand why it matters whether you get the literal user input: myfunc('a', 'b', 1, mykwarg='hello'), or the equivalent myfunc(a=a,b=b,c=1,mykwarg=hello), you can always retrofit the quotes, and strip the named argument association? What's your use-case? Are you simply playing around with the REPL, or does it actually break anything? (if so, provide an example and explain what it breaks and why) And can you give any compelling reason why not use the REPL? If not, this isn't really a real question, IMO. Commented May 6, 2019 at 10:14
  • I think you can use some of the stuff in my answer here to achieve what you want. Commented May 6, 2019 at 10:25
  • @smci question edited to add the use case Commented May 6, 2019 at 11:27

1 Answer 1

1

Finally I answer my own question, hopefully it can help someone else one day.

I decided to try to go the dis way, ie. "disassembling" the Python code object of the outer frame calling my function, to see how it has been called to be able to reconstruct the command line:

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

Example:

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

It is not a complete answer, since it cannot handle some argument types like dict or list... I maybe will continue on this or change my mind and do differently.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.