可能不是可取的, 但是这个can至少在某种程度上是通过解析调用者的字节码来完成的。具体来说,您需要使用例如获取调用者的堆栈帧。frame = inspect.stack()[-2][0]
,然后看看frame.f_code.co_code
对于原始字节码和frame.f_lasti
为该帧中执行的最后一条指令的索引 - 这将是CALL_FUNCTION
导致您的函数被调用的操作码。 (只要你还没有回来——之后f_lasti
将随着调用者框架中执行的进行而更新)
现在,解析字节码并不难,至少在您开始重建控制流之前(如果您可以假设被调用者不使用例如三元运算符,则可以避免这种情况,and
or or
在调用你的函数的参数中) - 但是虽然它本身并不难,但如果你之前没有做过类似的事情(即玩弄事物的内部结构,或其他稍微“低级”的东西) ),这可能是一座需要攀登的高山,因人而异;这可能不会是一个快速的练习。
那么,并发症怎么办?首先,字节码是 CPython 的实现细节,因此这在其他解释器(例如 Jython)中不起作用。此外,字节码可以从 CPython 版本更改为下一个版本,因此大多数 CPython 版本需要稍微不同的解析代码。
之前我也说过“在某种程度上”,这是什么意思呢?嗯,我已经提到处理条件可能很困难。此外,如果值没有存储在变量中,则无法获取变量名称!例如,您的函数可能被称为f(1, 2, 3)
, f(mylist[0], mydict['asd'], myfunc())
or f(*make_args(), **make_kwargs())
- 在前两种情况下,您可能只是决定您真正想知道的是与每个参数对应的表达式,而不是变量名......但是最后一种情况呢?任一表达式可能对应于多个参数,并且您不知道哪个参数来自哪个表达式!一般来说,你can't知道——因为这些值来自函数调用,所以如果不再次调用函数就无法查看它们——并且没有任何东西可以保证函数不会产生副作用或者它们会再次返回相同的值。或者,就像之前的条件一样,您可能可以假设调用者没有执行任何操作。
简而言之,在某种程度上,这是可能的,而且甚至可能不是特别难做到,至少如果你知道自己在做什么的话,但它肯定不会simple, fast, general nor 可取的.
Edit:如果,出于某种原因——毕竟——你still感觉你出于某种原因想要这样做(或者如果你只是好奇它会是什么样子),这里有一个极其有限的概念验证可以帮助您入门。 (仅适用于 CPython3.4,仅位置参数,仅局部变量。此外,对于 get_instructions(),所以解析必须手动完成。此外,当您添加对更多操作码的支持时,它会变得越来越复杂 - 这可能取决于以前的操作码的结果)
import inspect, dis
def get_caller_arg_names():
"""Get the argument names used by the caller of our caller"""
frame = inspect.stack()[-2][0]
lasti, code = frame.f_lasti, frame.f_code
insns = list(dis.get_instructions(code))
for call_ind, insn in enumerate(insns):
if insn.offset == lasti:
break
else:
assert False, "Frame's lasti doesn't match the offset of any of its instructions!"
insn = insns[call_ind]
assert insn.opcode == dis.opmap['CALL_FUNCTION'], "Frame's lasti doesn't point to a CALL_FUNCTION instruction!"
assert not insn.arg & 0xff00, "This PoC doesn't support keyword arguments!"
argcount = insn.arg & 0xff
assert call_ind >= argcount, "Bytecode doesn't have enough room for loading all the arguments! (At least without magic)"
argnames = []
for insn in insns[call_ind-argcount:call_ind]:
assert insn.opcode == dis.opmap['LOAD_FAST'], "This PoC only supports direct local variables (LOAD_FAST) without any hijinks!"
argnames.append(insn.argval)
return argnames
if __name__ == '__main__':
def callee(arg1, arg2, arg3):
print(get_caller_arg_names())
def caller():
a, b, c = 1, 2, 3
callee(a, b, c)
caller()