2013-05-19 60 views
6

我想代码,可以分析一个函数调用是这样的:解析Python函数调用来获取参数的位置

whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs) 

,并返回各自的每个参数的位置,在这种情况下foobaz()'puppet'24+2meow=3*meowargs**meowargs

我尝试使用_ast模块,它似乎只是工作的事情,但不幸的是有问题。例如,在一个像自己的函数调用baz()这样的参数中,我找不到一个简单的方法来获得它的长度。 (即使我找到了一个,我也不想为每一种不同类型的论证提供一些特殊情况。)

我也查看了tokenize模块,但看不到如何使用它来获取参数。

任何想法如何解决这个问题?

+0

“,并返回在这种情况下,每个论点的位置都是'foo','baz()','puppet'','24 + 2','meow = 3','* meowargs','** meowargs'。你想要返回什么?你怎么知道你的电话会是?为了什么用途?目前还不清楚你想做什么 – octoback

+0

我不确定你想要做什么,但我很确定正确,最好,最稳健的方法是看AST(最好通过'ast'模块,'_ast'是一个实现细节,'ast'增加了一些有用的功能。您需要了解AST和树遍历的概念,但如果没有这一点,无论如何您肯定会产生一个缓慢,复杂,有限,脆弱的解决方案。 – delnan

+0

@antitrust位置,即字符串中开始和结束的索引。用于IDE脚本。我无法弄清楚你的问题。 –

回答

3

该代码使用的ast组合(找到初始参数偏移)和正则表达式(以确定的参数边界):

import ast 
import re 

def collect_offsets(call_string): 
    def _abs_offset(lineno, col_offset): 
     current_lineno = 0 
     total = 0 
     for line in call_string.splitlines(): 
      current_lineno += 1 
      if current_lineno == lineno: 
       return col_offset + total 
      total += len(line) 
    # parse call_string with ast 
    call = ast.parse(call_string).body[0].value 
    # collect offsets provided by ast 
    offsets = [] 
    for arg in call.args: 
     a = arg 
     while isinstance(a, ast.BinOp): 
      a = a.left 
     offsets.append(_abs_offset(a.lineno, a.col_offset)) 
    for kw in call.keywords: 
     offsets.append(_abs_offset(kw.value.lineno, kw.value.col_offset)) 
    if call.starargs: 
     offsets.append(_abs_offset(call.starargs.lineno, call.starargs.col_offset)) 
    if call.kwargs: 
     offsets.append(_abs_offset(call.kwargs.lineno, call.kwargs.col_offset)) 
    offsets.append(len(call_string)) 
    return offsets 

def argpos(call_string): 
    def _find_start(prev_end, offset): 
     s = call_string[prev_end:offset] 
     m = re.search('(\(|,)(\s*)(.*?)$', s) 
     return prev_end + m.regs[3][0] 
    def _find_end(start, next_offset): 
     s = call_string[start:next_offset] 
     m = re.search('(\s*)$', s[:max(s.rfind(','), s.rfind(')'))]) 
     return start + m.start() 

    offsets = collect_offsets(call_string) 

    result = [] 
    # previous end 
    end = 0 
    # given offsets = [9, 14, 21, ...], 
    # zip(offsets, offsets[1:]) returns [(9, 14), (14, 21), ...] 
    for offset, next_offset in zip(offsets, offsets[1:]): 
     #print 'I:', offset, next_offset 
     start = _find_start(end, offset) 
     end = _find_end(start, next_offset) 
     #print 'R:', start, end 
     result.append((start, end)) 
    return result 

if __name__ == '__main__': 
    try: 
     while True: 
      call_string = raw_input() 
      positions = argpos(call_string) 
      for p in positions: 
       print ' ' * p[0] + '^' + ((' ' * (p[1] - p[0] - 2) + '^') if p[1] - p[0] > 1 else '') 
      print positions 
    except EOFError, KeyboardInterrupt: 
     pass 

输出:

whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs) 
     ^^ 
      ^^
        ^ ^
          ^^ 
            ^^
              ^ ^
                 ^ ^
[(9, 12), (14, 19), (21, 29), (31, 35), (37, 43), (45, 54), (56, 66)] 
f(1, len(document_text) - 1 - position) 
^
    ^       ^
[(2, 3), (5, 38)] 
+0

令人印象深刻的破解。我希望有可能创建一个不使用正则表达式的解决方案(因为它通常是这类任务的糟糕工具),但我接受它可能是不可能的。 –

+0

然而,你的解决方案失败了''Foo(x = y,\ n ** kwargs)“'。 –

+0

我已更新我的答案。 – utapyngo

0

您可能想要为函数的函数调用获取抽象语法树。

Here is a python recipe to do so,基于ast模块。

Python的ast模块用于解析代码字符串并创建一个ast 节点。然后,它通过生成的ast.AST节点来查找使用NodeVisitor子类的 功能。

功能explain进行解析。这里是你分析你的函数调用,你会得到什么

>>> explain('mymod.nestmod.func("arg1", "arg2", kw1="kword1", kw2="kword2", 
     *args, **kws') 
    [Call( args=['arg1', 'arg2'],keywords={'kw1': 'kword1', 'kw2': 'kword2'}, 
     starargs='args', func='mymod.nestmod.func', kwargs='kws')] 
+1

我看不出有什么帮助。例如,如果你的一个参数是一个函数调用本身,你怎么知道它的开始位置和结束位置? –

0

如果我理解正确的,从你的榜样,你想要的东西,如:

--> arguments("whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowkwds)") 
{ 
    'foo': slice(9, 12), 
    'baz()': slice(14, 19), 
    '24+2': slice(21, 29), 
    'meow=3': slice(32, 38), 
    '*meowargs': slice(41, 50), 
    '**meowkwds': slice(53, 63), 
} 

请注意,我改变了你的最后一个参数的名称,因为你不能有两个同名的参数。

如果这是你想要的,那么你需要有问题的原始字符串(不应该是一个问题,如果你建立一个IDE),并且你需要一个字符串解析器。一个简单的状态机应该这样做。

+0

我很感谢你试图帮助我,但当你说“一个简单的状态机应该做的伎俩”时,我不知道你的意思,以及如何构建它,所以它会实际工作并返回我想要的结果。 (是的,我有字符串。) –

+0

(我的确了解了高中的状态机,但是从那里到工作解决方案的方式对我来说并不清楚。) –

+0

@RamRachum:他们不是很难 - 话虽如此,我从来没有实际执行过。我会看看我是否无法抽出时间让他们在一起。 –