【问题标题】:How to get the original name of a positional parameter in Python?如何在 Python 中获取位置参数的原始名称?
【发布时间】:2015-04-01 12:41:11
【问题描述】:

在 Python 中,是否可以自动获取传递给当前函数的变量的原始名称作为位置参数?

方便调试。例如,在一个漂亮的打印函数中,为每个转储值的输出加上其原始名称的前缀,而不是让调用者手动指定它。

获得类似结果的最明显方法可能是使用关键字参数,例如:

# caution: Python3 code
def pprint_func(**kwargs):
    for n, v in kwargs.items():
        print("{}: {}".format(n, str(v)))

pprint_func(the_name="a value")
# this will output: "the_name: a value"

但这很不方便,因为我们必须手动命名每个参数:)

我宁愿尝试一些魔法,例如:

a_var = "Foo"
pprint_func(a_var, some_cat_func("Hello", "World"))

pprint_func 函数会找到原始名称,然后在转储实际值之前打印它:

a_var: 'Foo'
some_cat_func: 'Hello World'

如何在 Python3.x 中实现这一点?

我假设我们需要访问调用上下文的源代码。 注意,像手动对 Python 源代码进行词法分析/解析这样非常肮脏的选项绝对是不行的。

注意:此问题与previous one of mine 有关。我创建了一个新问题,因为该主题发生了变化。

另外,我发现this answer 很有趣,但如果传递的参数是调用的直接结果(即:pprint_func(result())),它就不起作用,导致我的问题的解决方案不完整。而原海报提供的final solution 也够脏了。

【问题讨论】:

    标签: python debugging python-3.x introspection


    【解决方案1】:

    看起来这样做的唯一方法是遍历调用者上下文的AST,搜索对当前函数的调用。然后,由于 AST 的结构,我们可以轻松找到我们需要的关于位置参数的所有信息。

    我不会将此答案标记为已接受,希望 Python 大师会通过并说出真相

    我已经制作了一个可以解决问题的实现,并且在通常情况下工作得很好。该代码绝对值得一些改进,尤其是 find_caller_node 部分,因为它尽可能精确,而不是 100%。

    我还必须 read 调用者的完整模块,因为 inspect.getsource 有时没有返回完整的源代码块(例如:调用者直接位于 __main__;Python v3.4.2)。是错误还是功能?

    无论如何,请不要对代码过于苛刻,因为它应该仅用于调试和教育目的。

    您可以找到latest version here

    这是为后代复制/粘贴的版本:

    #!/usr/bin/env python3
    #
    # pydump
    # A Python3 pretty-printer that also does introspection to detect the original
    # name of the passed variables
    #
    # Jean-Charles Lefebvre <polyvertex@gmail.com>
    # Latest version at: http://gist.github.com/polyvertex (pydump)
    #
    # Usage:
    #     dbg_dump(
    #         my_var, None, True, 123, "Bar", (4, 5, 6), fcall(), hello="world")
    # Result:
    #     my_var: 'Foo'
    #     None: None
    #     Bool: True
    #     Num: 123
    #     Str: 'Bar'
    #     Tuple: (4, 5, 6)
    #     fcall(): "Function's Result"
    #     hello: 'world'
    #
    
    import sys
    import pprint
    import inspect
    import ast
    
    def dbg_dump(
            *args,
            dumpopt_stream=sys.stderr,
            dumpopt_forcename=True,
            dumpopt_pformat={'indent': 2},
            dumpopt_srcinfo=1,
            **kwargs):
        """
        Pretty-format every passed positional and named parameters, in that order,
        prefixed by their **original** name (i.e.: the one used by the caller), or
        by their type name for literals.
    
        Depends on the *pprint*, *inspect* and *ast* modules, which are part of the
        Python3 standard library.
    
        Jean-Charles Lefebvre <polyvertex@gmail.com>
        Latest version at: http://gist.github.com/polyvertex (pydump)
    
        Note that the names of the keyword arguments you want to dump must not start
        with "dumpopt_" since this prefix is used internally to differentiate
        options over values to dump.
    
        Also, the introspection code won't behave as expected if do recursive calls
        to this function.
    
        Options can be passed as keyword arguments to tweak behavior and output
        format:
            dumpopt_stream
                May you wish to print() the result directly, you can pass a stream
                object (e.g.: sys.stdout) through this option, that will be given
                to print()'s "file" keyword argument.
                You can also specify None in case you just want the output string
                to be returned without further ado.
            dumpopt_forcename
                A boolean value to indicate wether you want every dumped value to
                be prepended by its name (i.e.: its name or its type).
                If False, only non-literal values will be named.
            dumpopt_forcename
                The dictionary of keyword arguments to give to pprint.pformat()
            dumpopt_srcinfo
                Specify a false value (None, False, zero) to skip caller's info.
                Specify 1 to output caller's line number only.
                Specify 2 to output caller's file name and line number.
                Specify 3 or greater to output caller's file path and line number.
    
        Example:
            dbg_dump(
                my_var, None, True, 123, "Bar", (4, 5, 6), fcall(), hello="world")
        Result:
            my_var: 'Foo'
            None: None
            Bool: True
            Num: 123
            Str: 'Bar'
            Tuple: (4, 5, 6)
            fcall(): "Function's Result"
            hello: 'world'
        """
        try:
            def _find_caller_node(root_node, func_name, last_lineno):
                # find caller's node by walking down the ast, searching for an
                # ast.Call object named func_name of which the last source line is
                # last_lineno
                found_node = None
                lineno = 0
                def _luke_astwalker(parent):
                    nonlocal found_node
                    nonlocal lineno
                    for child in ast.iter_child_nodes(parent):
                        # break if we passed the last line
                        if hasattr(child, "lineno") and child.lineno:
                            lineno = child.lineno
                        if lineno > last_lineno:
                            break
                        # is it our candidate?
                        if (isinstance(child, ast.Name)
                                and isinstance(parent, ast.Call)
                                and child.id == func_name):
                            found_node = parent
                            break
                        _luke_astwalker(child)
                _luke_astwalker(root_node)
                return found_node
    
            frame = inspect.currentframe()
            backf = frame.f_back
            this_func_name = frame.f_code.co_name
            #this_func = backf.f_locals.get(
            #    this_func_name, backf.f_globals.get(this_func_name))
    
            # get the source code of caller's module
            # note that we have to reload the entire module file since the
            # inspect.getsource() function doesn't work in some cases (i.e.:
            # returned source content was incomplete... Why?!).
            # --> is inspect.getsource broken???
            #     source = inspect.getsource(backf.f_code)
            #source = inspect.getsource(backf.f_code)
            with open(backf.f_code.co_filename, "r") as f:
                source = f.read()
    
            # get the ast node of caller's module
            # we don't need to use ast.increment_lineno() since we've loaded the
            # whole module
            ast_root = ast.parse(source, backf.f_code.co_filename)
            #ast.increment_lineno(ast_root, backf.f_code.co_firstlineno - 1)
    
            # find caller's ast node
            caller_node = _find_caller_node(ast_root, this_func_name, backf.f_lineno)
            if not caller_node:
                raise Exception("Caller's AST node not found")
    
            # keep some useful info for later
            src_info = {
                'file': backf.f_code.co_filename,
                'name': (
                    backf.f_code.co_filename.replace("\\", "/").rpartition("/")[2]),
                'lineno': caller_node.lineno}
    
            # if caller's node has been found, we now have the AST of our parameters
            args_names = []
            for arg_node in caller_node.args:
                if isinstance(arg_node, ast.Name):
                    args_names.append(arg_node.id)
                elif isinstance(arg_node, ast.Attribute):
                    if hasattr(arg_node, "value") and hasattr(arg_node.value, "id"):
                        args_names.append(arg_node.value.id + "." + arg_node.attr)
                    else:
                        args_names.append(arg_node.attr)
                elif isinstance(arg_node, ast.Subscript):
                    args_names.append(arg_node.value.id + "[]")
                elif (isinstance(arg_node, ast.Call)
                        and hasattr(arg_node, "func")
                        and hasattr(arg_node.func, "id")):
                    args_names.append(arg_node.func.id + "()")
                elif dumpopt_forcename:
                    if (isinstance(arg_node, ast.NameConstant)
                            and arg_node.value is None):
                        args_names.append("None")
                    elif (isinstance(arg_node, ast.NameConstant)
                            and arg_node.value in (False, True)):
                        args_names.append("Bool")
                    else:
                        args_names.append(arg_node.__class__.__name__)
                else:
                    args_names.append(None)
        except:
            src_info = None
            args_names = [None] * len(args)
    
        args_count = len(args) + len(kwargs)
    
        output = ""
        if dumpopt_srcinfo and src_info:
            if dumpopt_srcinfo <= 1:
                fmt = "D({2}):"
            elif dumpopt_srcinfo == 2:
                fmt = "{1}({2}):"
            else:
                fmt = "{0}({2}):"
            output += fmt.format(
                src_info['file'], src_info['name'], src_info['lineno'])
            output += "\n" if args_count > 1 else " "
        else:
            src_info = None
    
        for name, obj in zip(
                args_names + list(kwargs.keys()),
                list(args) + list(kwargs.values())):
            if name and name.startswith("dumpopt_"):
                continue
            if src_info and args_count > 1:
                output += "  "
            if name:
                output += name + ": "
            output += pprint.pformat(obj, **dumpopt_pformat) + "\n"
    
        if dumpopt_stream:
            print(output, end="", file=dumpopt_stream)
            return None # explicit is better than implicit
        else:
            return output.rstrip()
    
    
    if __name__ == "__main__":
        def fcall():
            return "Function's Result"
        my_var = "Foo"
        dbg_dump(
            my_var, None, True, 123, "Bar", (4, 5, 6), fcall(),
            dbg_dump(1, dumpopt_stream=None), hello="world")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-03
      • 2016-02-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-15
      • 1970-01-01
      相关资源
      最近更新 更多