我想将一个表示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模板函数。)
答案 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']