【问题标题】:How to get variable names of function call如何获取函数调用的变量名
【发布时间】:2018-09-05 12:10:28
【问题描述】:

我将编写一个装饰器来评估传递给函数调用的变量的实际名称(而不是它们的值)。

在下面,您会找到一个代码框架,这让我更清楚我想要做什么。

import functools

def check_func(func):
    # how to get variable names of function call
    # s.t. a call like func(arg1, arg2, arg3)
    # returns a dictionary {'a':'arg1', 'b':'arg2', 'c':'arg3'} ?
    pass

def my_decorator(func):
    @functools.wraps(func)
    def call_func(*args, **kwargs):
        check_func(func)
        return func(*args, **kwargs)

    return call_func

@my_decorator
def my_function(a, b, c):
    pass

arg1='foo'
arg2=1
arg3=[1,2,3]
my_function(arg1,arg2,arg3)

【问题讨论】:

标签: python function variables names


【解决方案1】:

你不能真正得到你想要的。

调用函数的方法有很多种,您甚至不会获得单个值的变量名称。例如,当您在调用中使用字面值时,名称会是什么,所以:

my_function('foo', 10 - 9, [1] + [2, 3])

或者当您使用带有 * 的参数扩展值的列表时:

args = ['foo', 1, [1, 2, 3]]
my_function(*args)

或者当您使用functools.partial() object 将一些参数值绑定到可调用对象时:

from functools import partial
func_partial = partial(my_function, arg1, arg2)
func_partial(arg3)

函数传递的是对象(值),而不是变量。仅由名称组成的表达式可能已用于生成对象,但这些对象与变量无关。

Python 对象可以有许多不同的引用,所以仅仅因为调用使用了arg1,并不意味着在别处不会有对该对象的其他引用会更有趣你的代码。

您可以尝试分析调用该函数的代码(inspect module 可以为您提供access to the call stack),但前提是源代码可用。调用代码可以使用 C 扩展,或者解释器只能访问 .pyc 字节码文件,而不是原始源代码。您仍然必须追溯并分析调用表达式(并不总是那么简单,函数也是对象,可以存储在容器中并稍后检索以动态调用)并从那里找到所涉及的变量如果有的话完全没有

对于琐碎的情况,调用只使用直接的位置参数名称,并且整个调用仅限于一行源代码,您可以使用inspect.stack()ast module 将源解析为有用的东西以供分析:

import inspect, ast

class CallArgumentNameFinder(ast.NodeVisitor):
    def __init__(self, functionname):
        self.name = functionname
        self.params = []
        self.kwargs = {}

    def visit_Call(self, node):
        if not isinstance(node.func, ast.Name):
            return  # not a name(...) call
        if node.func.id != self.name:
            return  # different name being called
        self.params = [n.id for n in node.args if isinstance(n, ast.Name)]
        self.kwargs = {
            kw.arg: kw.value.id for kw in node.keywords
            if isinstance(kw.value, ast.Name)
        }

def check_func(func):
    caller = inspect.stack()[2]  # caller of our caller
    try:
        tree = ast.parse(caller.code_context[0])
    except SyntaxError:
        # not a complete Python statement
        return None
    visitor = CallArgumentNameFinder(func.__name__)
    visitor.visit(tree)
    return inspect.signature(func).bind_partial(
        *visitor.params, **visitor.kwargs)

再次强调:这只适用于最基本的调用,其中调用仅包含一行,并且被调用的名称与函数名称匹配。它可以扩展,但这需要做很多工作。

对于您的具体示例,这会产生<BoundArguments (a='arg1', b='arg2', c='arg3')>,因此会产生inspect.BoundArguments instance。使用 .arguments 获取带有名称-值对的 OrderedDict 映射,或使用 dict(....arguments) 将其转换为常规字典。

您必须以不同的方式考虑您的具体问题。装饰器并不是要作用于代码调用,它们作用于被装饰的对象。该语言中还有许多其他强大的功能可以帮助您处理调用上下文,但装饰器不是。

【讨论】:

  • 不正确。您可以使用 func.__code__.co_varnames 获取函数参数的名称
  • @Gnoliz:不,你误解了这个问题。他们没有要求函数的参数名称,也没有要求值。注意问题中的{'a':'arg1', 'b':'arg2', 'c':'arg3'} 部分,arg1arg2arg3 是调用表达式my_function(arg1,arg2,arg3) 中的变量。
  • 我明白了。你是对的。他要求的是函数本身之外的东西,这似乎是不可能的。
  • 很遗憾 :-( 但我已经在期待这样的答案了。我只是想确定一下。然后,我将不得不考虑一个解决方法。感谢您的回答
猜你喜欢
  • 2022-01-03
  • 2016-10-04
  • 1970-01-01
  • 2023-01-12
  • 2021-10-25
  • 1970-01-01
  • 2011-02-14
  • 1970-01-01
  • 2011-09-09
相关资源
最近更新 更多