在 python 函数内部,使用表示 func 对象的 var 来获取 func 的参数和提供的值

From inside a python function, use a var representing the func object, to get the func's parameters and the values supplied

提问人:Marc B. Hankin 提问时间:10/27/2023 更新时间:10/27/2023 访问量:66

问:

函数中代码的第一行 'func' 下面是一个 print 语句。它打印/生成元组和字典之间的以下交叉,(在第一个等号的右侧)包含所有 func 的参数以及这些参数的默认值

str(inspect.signature(func)) = (a='my', b='dog', c='chews', d='bones')

我想修改“func”第一行的 print 语句(与下面的 print 语句 #2 不同):

  1. 因此,每次调用 'func' 时,print 语句都会打印参数的名称以及:[i] 提供的值(如果它们与参数的默认值不同),以及 [ii] 默认值如果未提供其他值);
  2. 通过将字符串“func”替换为表示函数对象的代码变量,其中函数对象是代码/变量所在的函数(在本例中为“func”),作为对象。为了说明这一点,假设 X = PSEUDO_CODE_REPRESENTING_THIS_FUNCTION_OBJECT,并且以下行是函数中的第一行代码:
print('str(inspect.signature(X) = ' + str(inspect.signature(X))

下面是函数 'func'(在 'func' 的第一行包含一个 print 语句),在 'func' 的定义之后,第二个 print 语句(在代码的最后一行)向 'func' 提供非默认值:

def func(a='my', b='dog', c='chews', d='bones'):
    print('str(inspect.signature(func)) = ' + str(inspect.signature(func)))

    return a + ' ' + b + ' ' + c + ' ' + d


print('Statement #2: ' + func(a='her', b='cat', c='drinks', d='milk'))

任何建议将不胜感激,最好附有链接或一些解释。

Python 属性 检查

评论

0赞 Mous 10/27/2023
这是一个很好的问题,马克。我现在不在我的电脑上,但我过去遇到过这种情况,我想我有一些代码应该可以解决问题。我很快就会抓住它。
0赞 Abdul Aziz Barkat 10/27/2023
你的问题不是很清楚,也缺乏重点(问多个问题)。我不知道你的第一点是什么意思,具体来说,输出应该是什么样子?一些例子可能会有所帮助。第二点很令人困惑:您是否在寻求一种方法在不知道其名称的情况下获取变量中的当前函数对象?如果是这样,请参阅:stackoverflow.com/questions/4492559/...
0赞 Marc B. Hankin 10/28/2023
@AbdulAzizBarkat, [1] 非常感谢你通知我不要问 2 个问题,并向我展示了我徒劳地搜索了几个小时的链接(显然我没有使用正确的搜索词),我会从中学到很多东西。我试图创造一种方法来促进调试,并获得关于我的方法是否不明智的输入。我的下一个目标是了解有关调用堆栈以及检查和回溯模块的更多信息。你已经帮了我很多,但我欢迎关于如何学习这些材料的任何其他建议。

答:

1赞 Mous 10/27/2023 #1

你应该做什么

在正常情况下,你绝对应该使用装饰器来做到这一点,因为你试图做的事情是一个非常糟糕的主意,需要弄乱 Python 中更坚韧的部分,同时也是意大利面条代码的巅峰之作。

下面是一个执行上述操作的装饰器:

import inspect

def wrap(f):
    def inner(*args, **kwargs):
        print(f'str(inspect.getsignature({f.__name__})) =', inspect.signature(f)) # use __name__ to get its' name
        return f(*args, **kwargs)
    return inner

@wrap
def func(a='my', b='dog', c='chews', d='bones'):
    return a + ' ' + b + ' ' + c + ' ' + d

然而,这并不好玩!

你不应该做什么

Python 使用帧来实现调用堆栈,并提供了一个方便的库来摆弄它们,你已经遇到过 - 检查库。

inspect.currentframe返回一个 FrameInfo 对象,该对象将为我们提供调用该对象的帧 - 在本例中为表示函数的帧。

然后,我们去掉该帧的属性,它是一个代码对象 - 每个函数背后的肉。但是,代码对象还不是函数,你要求一个。令人恼火的是,没有办法将默认参数从框架中提取出来。f_code

我们只需在调用堆栈中跳一个,并使用我们从中获得的名称来访问调用方的名称,就可以解决这个问题。这为我们提供了我们的功能。f_code.co_namef_locals

下面是一个实现:

import inspect

def func(a='my', b='dog', c='chews', d='bones'):
    frame = inspect.currentframe()

    name = frame.f_code.co_name
    previous_frame_locals = frame.f_back.f_locals
    f = previous_frame_locals[name]

    print(f'str(inspect.getsignature({name})) =', inspect.signature(func))

    return a + ' ' + b + ' ' + c + ' ' + d

不过这很啰嗦,你想要一个表情。

import inspect

def func(a='my', b='dog', c='chews', d='bones'):
    print(f'str(inspect.getsignature({(name := (frame := inspect.currentframe()).f_code.co_name)})) =', inspect.signature(frame.f_back.f_locals[name]))

    return a + ' ' + b + ' ' + c + ' ' + d

问题是,这是完全不可读的。我们可以做一个辅助函数!

import inspect

def helper():
    frame = inspect.currentframe().f_back # make an additional step back up the stack

    name = frame.f_code.co_name
    previous_frame_locals = frame.f_back.f_locals
    f = previous_frame_locals[name]
    
    print(f'str(inspect.getsignature({name})) =', inspect.signature(func))

def func(a='my', b='dog', c='chews', d='bones'):
    helper()

    return a + ' ' + b + ' ' + c + ' ' + d

这可能是最好的解决方案,即使它仍然是针对 Python 的完全犯罪。在代码中,只需使用上面的装饰器即可。

最后一点,您实际上并不需要获取当前帧 - 您可以通过引发错误并使用回溯来获取帧来执行恶作剧。如果我们做其他操作,它将为我们节省导入,这将很好。它也是不可读的,所以不要这样做。无论如何,出于教育目的,它在这里。inspectinspect.signature

import inspect

def helper():
    # get the frame
    try:
        raise # anything that raises an exception, like 1/0, will work.
    except Exception as e:
        traceback = e.__traceback__
        helper_frame = traceback.tb_frame
        frame = helper_frame.f_back # make an additional step back up the stack
    
    name = frame.f_code.co_name
    previous_frame_locals = frame.f_back.f_locals
    f = previous_frame_locals[name]
    
    print(f'str(inspect.getsignature({name})) =', inspect.signature(func))

也要小心清除帧 - 如果不这样做,它可能会创建参考周期,而内存泄漏是不好的juju。

所有这些都是特定于 CPython 的,并且会在 API 更新时中断,这是定期的。

只需使用装饰器即可。

评论

0赞 Marc B. Hankin 10/28/2023
谢谢。我最想要的是教育,而你给了我。我需要一段时间才能完全摄取您向我展示的内容,并遵循面包屑,以便我对这些问题有更好的理解。但我对你感激不尽。