将字符串解析为Python参数列表

时间:2018-04-08 21:42:54

标签: python parsing

摘要

我想将一个表示Python参数列表的字符串解析为一个可以转发给函数调用的表单。

详细版本

我正在构建一个应用程序,我希望能够从文本字符串中解析出参数列表,然后将其转换为*args,**kwargs模式以转发到实际方法。例如,如果我的文本字符串是:

"hello",42,helper="Larry, the \"wise\""

解析后的结果可与以下内容相媲美:

args=['hello',42]
kwargs={'helper': 'Larry, the "wise"'}

我知道Python的ast模块,但它似乎只提供了解析整个语句的机制。我可以通过围绕它制作声明来伪装它,例如

ast.parse('f("hello",42,helper="Larry, the \"wise\"")'

然后从Call节点中拉出相关字段,但这似乎是一个非常多的迂回工作。

有没有办法从Python AST中解析一个已知的节点类型,或者是否有更简单的方法来获取此功能?

如果有帮助,我只需要能够支持数字和字符串参数,尽管字符串需要支持嵌入式逗号和转义引号等。

如果在Python中存在用于构建词法分析器和解析器的现有模块,我也可以定义自己的AST,但显然我更喜欢使用已经存在的功能并且已经过测试正确等等。 / p>

注意:许多答案都集中在如何存储已解析的结果上,但这并不是我所关心的;它是我试图解决的解析本身,理想情况下我自己没有编写完整的解析器引擎。

此外,我的应用程序已经在使用Jinja,它在自己的模板解析器中有一个Python-ish表达式解析器,虽然我不清楚如何使用它来解析一个子表达式,如这个。 (遗憾的是,这不是模板中的内容,而是自定义Markdown过滤器,我希望语法能够尽可能地匹配其匹配的Jinja模板函数。)

4 个答案:

答案 0 :(得分:2)

我认为ast.parse是您最好的选择。

如果参数是用空格分隔的,我们可以使用shlex.split

>>> shlex.split(r'"hello" 42 helper="Larry, the \"wise\""')
['hello', '42', 'helper=Larry, the "wise"']

但不幸的是,这并没有用逗号分开:

>>> shlex.split(r'"hello",42,helper="Larry, the \"wise\""')
['hello,42,helper=Larry, the "wise"']

我还考虑过使用ast.literal_eval,但这不支持关键字参数:

>>> ast.literal_eval(r'"hello",42')
('hello', 42)
>>> ast.literal_eval(r'"hello",42,helper="Larry, the \"wise\""')
Traceback (most recent call last):
  File "<unknown>", line 1
    "hello",42,helper="Larry, the \"wise\""
                     ^
SyntaxError: invalid syntax

我想不出任何支持位置和关键字参数的python文字。

缺乏更好的想法,这是使用ast.parse的解决方案:

import ast

def parse_args(args):
    args = 'f({})'.format(args)
    tree = ast.parse(args)
    funccall = tree.body[0].value

    args = [ast.literal_eval(arg) for arg in funccall.args]
    kwargs = {arg.arg: ast.literal_eval(arg.value) for arg in funccall.keywords}
    return args, kwargs

输出:

>>> parse_args(r'"hello",42,helper="Larry, the \"wise\""')
(['hello', 42], {'helper': 'Larry, the "wise"'})

答案 1 :(得分:0)

您可以使用re和一个简单的类来跟踪令牌:

import re
class Akwargs:
   grammar = r'"[\w\s_]+"|"[\w\s,_"]+"|\d+|[a-zA-Z0-9_]+|\='
   def __init__(self, tokens):
      self.tokens = tokens
      self.args = []
      self.kwargs = {}
      self.parse()
   def parse(self):
      current = next(self.tokens, None)
      if current:
         check_next = next(self.tokens, None)
         if not check_next:
            self.args.append(re.sub('^"+|"+$', '', current))
         else:
            if check_next == '=':
               last = next(self.tokens, None)
               if not last:
                   raise ValueError("Expecting kwargs key")
               self.kwargs[current] = re.sub('^"|"$', '', last)
            else:
               self.args.extend(list(map(lambda x:re.sub('^"+|"+$', '', x), [current, check_next])))
         self.parse()

s = '"hello",42,helper="Larry, the \"wise\""'
tokens = iter(re.findall(Akwargs.grammar, s))
params = Akwargs(tokens)
print(params.args)
print(params.kwargs)

输出:

['hello', '42']
{'helper': 'Larry, the "wise"'}

完整测试:

strings = ['23,"Bill","James"', 'name="someone",age=23,"testing",300','"hello","42"',  "hello=42", 'foo_bar=5']
new_data = [(lambda x:[getattr(x, i) for i in ['args', 'kwargs']])(Akwargs(iter(re.findall(Akwargs.grammar, d)))) for d in strings]

输出:

[[['23', 'Bill', 'James'], {}], [['testing', '300'], {'age': '23', 'name': 'someone'}], [['hello', '42'], {}], [[], {'hello': '42'}], [[], {'foo_bar': '5'}]]

答案 2 :(得分:0)

您可以使用带有 eval 的函数来帮助您区分 args 和 kwargs:

def f(*args, **kwargs):
  return args, kwargs

import numpy as np
eval("f(1, 'a', x=np.int32)")

给你

((1, 'a'), {'x': <class 'numpy.int32'>})

答案 3 :(得分:-1)

这并不完全是你想要的,但它很接近。

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--helper')
>>> kwargs,args = parser.parse_known_args(["hello",'42','--helper="Larry, the \"wise\""'])
>>> vars(kwargs)
{'helper': '"Larry, the "wise""'}
>>> args
['hello', '42']