7

我想要可以分析这样的函数调用的代码:

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

并返回每个参数的位置,在本例foo中为 , baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs

我尝试使用该_ast模块,它似乎只是适合这项工作的东西,但不幸的是出现了问题。例如,在一个像baz()函数调用本身这样的参数中,我找不到一种简单的方法来获取它的长度。(即使我找到了一个,我也不希望针对每种不同的论点都使用一堆特殊情况。)

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

知道如何解决这个问题吗?

4

3 回答 3

6

此代码结合使用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)]
于 2013-05-22T08:02:01.690 回答
-1

您可能希望获取函数的函数调用的抽象语法树。

这是一个基于ast模块的 python 配方。

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')]
于 2013-05-19T15:16:41.690 回答
-1

如果我理解正确,从你的例子中你想要类似的东西:

--> 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,这应该不是问题),并且您需要一个字符串解析器。一个简单的状态机应该可以解决问题。

于 2013-05-21T21:21:59.870 回答