解析Python函数调用以获取参数位置

解析Python函数调用以获取参数位置,python,syntax,lexical-analysis,Python,Syntax,Lexical Analysis,我需要能够分析函数调用的代码,如下所示: whatever(foo, baz(), 'puppet', 24+2, meow=3, *meowargs, **meowargs) 并返回每个参数的位置,在本例中为foo,baz(),'puppet',24+2,meow=3,*meowargs,**meowargs 我试着使用\u ast模块,它似乎正适合这项工作,但不幸的是出现了问题。例如,在像baz()这样的参数中,它本身就是一个函数调用,我找不到一种简单的方法来获取它的长度。(即使我找到了一

我需要能够分析函数调用的代码,如下所示:

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

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

我试着使用
\u ast
模块,它似乎正适合这项工作,但不幸的是出现了问题。例如,在像
baz()
这样的参数中,它本身就是一个函数调用,我找不到一种简单的方法来获取它的长度。(即使我找到了一个,我也不希望每种不同的论点都有一大堆特殊的案例。)

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


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

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

,基于
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')]

如果我理解正确,从您的示例中,您希望类似于:

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

此代码使用
ast
(查找初始参数偏移量)和正则表达式(标识参数边界)的组合:

输出:

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)]

“并返回每个参数的位置,在本例中,
foo
baz()
'puppet'
24+2
meow=3
*meowargs
**meowargs
”您想返回什么?你怎么知道你的电话是什么?有什么用?你想做什么还不清楚,我也不确定你想做什么,但我很确定正确的、最好的、最健壮的方法是查看AST(最好通过
AST
模块,
\u AST
是一个实现细节,
AST
添加了一些有用的功能)。您需要了解ASTs和树遍历的概念,但如果不了解这一点,您肯定会产生一个缓慢、复杂、有限、脆弱的解决方案,不管怎样,它会影响位置,即它们在字符串中开始和结束的索引。使用的是IDE脚本。我想不出你关于这个电话的问题。还是不清楚你想要什么。您想要调用什么(即函数内部)还是可以调用什么(例如IDE尝试排列正确的参数)。请参见
foo
?我想要一个元组,其中第一项是
f
的位置,第二项是最后一个
o
的位置。我看不出这有什么帮助。例如,如果你的一个参数是函数调用本身,你如何知道它的开始位置和结束位置?我很感激你能帮助我,但是当你说“一个简单的状态机应该可以做到这一点”时,我不知道你的意思是什么,以及如何构建它,这样它才能真正工作并返回我想要的结果。(是的,我有这个字符串。)(我在高中时确实学习过状态机,但从它到工作解决方案的方法对我来说并不清楚。)@RamRachum:它们不太难——尽管如此,我从未真正实现过。我会看看我是否能抽出时间聚在一起。@EthanFurman:是的。这并不太难,但由于必须解析Python语法的相当一部分,因此可能会变得单调乏味。我会使用
pyparsing
ply
来代替。令人印象深刻的黑客。我希望有可能创建一个不使用regex的解决方案(因为它对于此类任务来说通常是一个不好的工具),但我承认这可能是不可能的。但是,您的解决方案在
“Foo(x=y,\n**kwargs)”
中失败。如果结尾数字再高一个可能更好——那么它可以直接用作字符串
片段,或
范围
参数。对于
“f(1,len(文档文本)-1-位置)”失败
。这将成为一个真正的黑客。它现在适用于
f(1,len(文档文本)-1-位置)
。我还考虑了@EthanFurman的评论。
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)]