2015-02-02 4 views
0

파이썬에서는 현재 함수에 전달 된 변수의 원래 이름을 위치 인수로 자동으로 가져올 수 있습니까?파이썬에서 위치 매개 변수의 원래 이름을 얻는 방법은 무엇입니까?

디버깅 목적으로 편리 할 수 ​​있습니다. 예를 들어 pretty-print 함수에서 호출자가 수동으로 지정하는 대신 모든 덤프 된 값의 출력 앞에 원래 이름을 접두사로 붙입니다.

아마에서 같은 키워드 인수를 사용하는 것입니다 비슷한 얻을 수있는 가장 확실한 방법 : 우리가 수동으로 각 인수 :

나는 것 이름을 가지고하지만 이것은 매우 불편

# 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 기능은 다음 원래 이름을 찾아 ACTU의 덤프하기 전에 인쇄 할 것 al 값 :

a_var: 'Foo' 
some_cat_func: 'Hello World' 

어떻게하면 Python3.x에서이를 수행 할 수 있습니까?

호출 컨텍스트의 소스 코드에 액세스해야한다고 가정합니다. 을 작성하십시오. 수동으로 파이썬 소스 코드를 렉싱/파싱하는 것과 같은 매우 더러운 옵션은 분명히 no-go입니다.

참고 :이 질문은 previous one of mine과 관련이 있습니다. 나는 주제가 진화했기 때문에 새로운 질문을 만들었다.

흥미롭게도 this answer이 흥미 롭습니다. 전달 된 매개 변수가 직접적인 결과 (즉, pprint_func(result())) 인 경우 문제가 해결되지 않아 작동하지 않습니다. 그리고 원래의 포스터에서 제공되는 최종solution은 가능한 한 더러운 것입니다.

답변

0

이 작업을 수행하는 유일한 방법은 발신자의 컨텍스트 AST을 통해 현재 기능에 대한 호출을 검색하는 것입니다. 그런 다음 AST의 구조 덕분에 우리는 우리의 입장에 대해 필요한 모든 정보를 쉽게 찾을 수 있습니다. 난 그냥 트릭을 수행하고 일반적인 꽤 잘 작동 구현을했습니다

파이썬 전문가가 진실을하여 통과하고 말할 것입니다 희망 접수로

나는하지 깃발이 대답을 할 사례. 코드의 정확성은 특히 find_caller_node 부분이 정확할 수 있으므로 정확해야합니다. 이는 100 %가 아닙니다.

inspect.getsource은 전체 소스 블록을 반환하지 않았기 때문에 read 호출자의 전체 모듈을 가져야했습니다 (예 : 발신자가 __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 <[email protected]> 
# 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 <[email protected]> 
    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") 
:

그리고 여기에는 후손을위한 복사/붙여 넣기 버전입니다

관련 문제