【问题标题】:parsing nested functions in python在python中解析嵌套函数
【发布时间】:2020-05-16 14:54:31
【问题描述】:
line = "add(multiply(add(2,3),add(4,5)),1)"

def readLine(line):
    countLeftBracket=0
    string = ""
    for char in line:
        if char !=")":
            string += char
        else:
            string +=char
            break

    for i in string:
        if i=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    else:
        return execute(string)

def cutString(string):
    countLeftBracket=0

    for char in string:
        if char!="(":
            string.replace(char,'')
        elif char=="(":
            string.replace(char,'')
            break

    for char in string:
        if char=="(":
            countLeftBracket+=1

    if countLeftBracket>1:
        cutString(string)
    elif countLeftBracket==1:
        return execute(string)

def add(num1,num2):
    return print(num1+num2)

def multiply(num1,num2):
    return print(num1*num2)

readLines(line)

我需要执行整个行字符串。我试图将括号内的每个函数一个一个地剪切并用结果替换它们,但我有点迷失了。不知道如何继续,我的代码得到了错误:

  File "main.py", line 26, in cutString                                                                                 
    if char!="(":                                                                                                       
RuntimeError: maximum recursion depth exceeded in comparison 

告诉我要搬到哪里,使用哪种方法?

【问题讨论】:

  • 预期输出是什么? execute 有吗?
  • 一般来说,我建议首先编写一个返回标记流的词法分析器。通过字符串替换进行解析并不是解决这个问题的方法。
  • @zamir 回答整个嵌套函数。在这种情况下,答案是 46,但我需要以这种格式处理更多随机输入。
  • @thebjorn 你能给我一些例子吗?我不是很熟悉。

标签: python parsing nested-function


【解决方案1】:

您可以使用生成器函数来构建一个非常简单的解析器:

import re, operator
line, f = "add(multiply(add(2,3),add(4,5)),1)", {'add':operator.add, 'multiply':operator.mul}
def parse(d):
   n = next(d, None)
   if n is not None and n != ')':
     if n == '(':
       yield iter(parse(d))
     else:
       yield n
     yield from parse(d)

parsed = parse(iter(re.findall('\(|\)|\w+', line)))
def _eval(d):
   _r = []
   n = next(d, None)
   while n is not None:
      if n.isdigit():
        _r.append(int(n))
      else:
        _r.append(f[n](*_eval(next(d))))
      n = next(d, None)
   return _r

print(_eval(parsed)[0])

输出:

46

【讨论】:

  • 非常感谢非常感谢
  • 文件“main.py”,第 4 行 if (n:=next(d, None)) is not None and n != ')': ^ SyntaxError: invalid syntax
  • 同样的问题,@Ajax1234 假设你有 python 3.8,你可能有一个旧版本
  • @BayramJumageldiyev 是的,我最初的解决方案使用了赋值表达式,它只在 Python 3.8 中可用。请查看我最近的编辑。
  • @UriGoren 实际上我最初使用的语法是仅在 Python 3.8 中可用的赋值表达式。我编辑了我的解决方案以兼容 Python 版本
【解决方案2】:

这是一个使用 pyparsing 的解决方案,因此扩展起来会容易得多:

from pyparsing import *

第一个方便函数(使用第二个标记函数并打印解析树以查看原因)

def tag(name):
    """This version converts ["expr", 4] => 4
       comment in the version below to see the original parse tree
    """
    def tagfn(tokens):
        tklist = tokens.asList()
        if name == 'expr' and len(tklist) == 1:
            # LL1 artifact removal
            return tklist
        return tuple([name] + tklist)
    return tagfn

# def tag(name):
#     return lambda tokens: tuple([name] + tokens.asList())

我们的词法分析器需要识别左右括号、整数和名称。这就是您使用 pyparsing 定义它们的方式:

LPAR = Suppress("(")
RPAR = Suppress(")")
integer = Word(nums).setParseAction(lambda s,l,t: [int(t[0])])
name = Word(alphas)

我们的解析器有函数调用,它接受零个或多个表达式作为参数。函数调用也是一个表达式,所以为了处理循环我们必须前向声明 expr 和 fncall:

expr = Forward()
fncall = Forward()

expr << (integer | fncall).setParseAction(tag('expr'))
fnparams = delimitedList(expr)

fncall << (name + Group(LPAR + Optional(fnparams, default=[]) + RPAR)).setParseAction(tag('fncall'))

现在我们可以解析我们的字符串(我们也可以在函数中添加空格和多于或少于两个参数):

line = "add(multiply(add(2,3),add(4,5)),1)"
res = fncall.parseString(line)

要查看返回的内容,您可以打印它,这称为解析树(或者,由于我们的标记函数已对其进行了简化,因此称为抽象语法树):

import pprint
pprint.pprint(list(res))

哪个输出:

[('fncall',
  'add',
  [('fncall',
    'multiply',
    [('fncall', 'add', [2, 3]), ('fncall', 'add', [4, 5])]),
   1])]

使用注释掉的标记功能(这只是处理更多工作而没有额外的好处):

[('fncall',
  'add',
  [('expr',
    ('fncall',
     'multiply',
     [('expr', ('fncall', 'add', [('expr', 2), ('expr', 3)])),
      ('expr', ('fncall', 'add', [('expr', 4), ('expr', 5)]))])),
   ('expr', 1)])]

现在定义我们程序可用的函数:

FUNCTIONS = {
    'add': lambda *args: sum(args, 0),
    'multiply': lambda *args: reduce(lambda a, b: a*b, args, 1),
}

# print FUNCTIONS['multiply'](1,2,3,4)   # test that it works ;-)

我们的解析器现在写起来非常简单:

def parse(ast):
    if not ast:  # will not happen in our program, but it's good practice to exit early on no input
        return

    if isinstance(ast, tuple) and ast[0] == 'fncall':
        # ast is here ('fncall', <name-of-function>, [list-of-arguments])
        fn_name = ast[1]          # get the function name
        fn_args = parse(ast[2])   # parse each parameter (see elif below)
        return FUNCTIONS[fn_name](*fn_args)  # find and apply the function to its arguments
    elif isinstance(ast, list):
        # this is called when we hit a parameter list
        return [parse(item) for item in ast]
    elif isinstance(ast, int):
        return ast

现在根据词法分析阶段的结果调用解析器:

>>> print parse(res[0])  # the outermost item is an expression
46

【讨论】:

    【解决方案3】:

    这样的声音可以用正则表达式解决。

    所以这是一个单一减少的例子

    import re, operator
    def apply(match):
       func_name = match.group(1) # what's outside the patentesis
       func_args = [int(x) for x in match.group(2).split(',')]
       func = {"add": operator.add, "multiply": operator.mul}
       return str(func[func_name](*func_args))
    def single_step(line):
       return re.sub(r"([a-z]+)\(([^()]+)\)",apply,line)
    

    例如:

    line = "add(multiply(add(2,3),add(4,5)),1)"
    print(single_step(line))
    

    会输出:

    加(乘(5,9),1)

    剩下要做的就是循环直到表达式为数字

    while not line.isdigit():
       line = single_step(line)
    print (line)
    

    会显示

    46

    【讨论】:

    • 从我的答案中复制整个代码,从上到下 - 它可以工作
    • 文件“main.py”,第 3 行,在 apply func_name = g[1] TypeError: '_sre.SRE_Match' object is not subscriptable
    • 文件“main.py”,第 8 行,单步返回 re.sub(r"([az]+)(([^()]+))",apply,line) 文件“/usr/lib/python3.4/re.py”,第 179 行,在 sub return _compile(pattern, flags).sub(repl, string, count)
    • 你正在使用 python2 !
    • 虽然你真的应该提到如果你没有使用最新版本
    猜你喜欢
    • 1970-01-01
    • 2018-09-12
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    • 2019-12-04
    • 2021-12-17
    • 1970-01-01
    相关资源
    最近更新 更多