问题
有没有办法将函数参数声明为非严格的(按名称传递)?
如果这不能直接实现:是否有任何辅助函数或装饰器可以帮助我实现类似的目标?
具体例子
这是一个可以试验的小玩具示例。
假设我想构建一个小型解析器组合器库,它可以处理以下带括号的算术表达式的经典语法(1
为简单起见,将数字替换为单个文字值):
num = "1"
factor = num
| "(" + expr + ")"
term = factor + "*" + term
| factor
expr = term + "+" + expr
| term
假设我将解析器组合器定义为一个对象,该对象具有一个方法,该方法parse
可以获取令牌列表、当前位置,并且要么引发解析错误,要么返回结果和新位置。我可以很好地定义一个ParserCombinator
提供+
(连接)和|
(替代)的基类。然后我可以定义接受常量字符串的解析器组合器,并实现+
and |
:
# Two kinds of errors that can be thrown by a parser combinator
class UnexpectedEndOfInput(Exception): pass
class ParseError(Exception): pass
# Base class that provides methods for `+` and `|` syntax
class ParserCombinator:
def __add__(self, next):
return AddCombinator(self, next)
def __or__(self, other):
return OrCombinator(self, other)
# Literally taken string constants
class Lit(ParserCombinator):
def __init__(self, string):
self.string = string
def parse(self, tokens, pos):
if pos < len(tokens):
t = tokens[pos]
if t == self.string:
return t, (pos + 1)
else:
raise ParseError
else:
raise UnexpectedEndOfInput
def lit(str):
return Lit(str)
# Concatenation
class AddCombinator(ParserCombinator):
def __init__(self, first, second):
self.first = first
self.second = second
def parse(self, tokens, pos):
x, p1 = self.first.parse(tokens, pos)
y, p2 = self.second.parse(tokens, p1)
return (x, y), p2
# Alternative
class OrCombinator(ParserCombinator):
def __init__(self, first, second):
self.first = first
self.second = second
def parse(self, tokens, pos):
try:
return self.first.parse(tokens, pos)
except:
return self.second.parse(tokens, pos)
到目前为止,一切都很好。然而,因为语法的非终结符号是以相互递归的方式定义的,我不能急切地展开所有可能的解析器组合树,我必须使用解析器组合器的工厂,并将它们包装成这样的东西:
# Wrapper that prevents immediate stack overflow
class LazyParserCombinator(ParserCombinator):
def __init__(self, parserFactory):
self.parserFactory = parserFactory
def parse(self, tokens, pos):
return self.parserFactory().parse(tokens, pos)
def p(parserFactory):
return LazyParserCombinator(parserFactory)
这确实允许我以非常接近 EBNF 的方式写下语法:
num = p(lambda: lit("1"))
factor = p(lambda: num | (lit("(") + expr + lit(")")))
term = p(lambda: (factor + lit("*") + term) | factor)
expr = p(lambda: (term + lit("+") + expr) | term)
它实际上有效:
tokens = [str(x) for x in "1+(1+1)*(1+1+1)+1*(1+1)"]
print(expr.parse(tokens, 0))
但是,p(lambda: ...)
每一行都有些烦人。有没有一些惯用的方法来摆脱它?如果有人能以某种方式通过“按名称”规则的整个 RHS,而不触发对无限相互递归的急切评估,那就太好了。
我试过的
我已经检查了核心语言中可用的内容:似乎只有if
,and
并且or
可以“短路”,如果我错了,请纠正我。
我试过看看其他非玩具示例库是如何做到这一点的。
例如, funcparserlib 使用显式前向声明来避免相互递归(查看 github README.md 示例代码中的
forward_decl
andvalue.define
部分)。使用
parsec.py
一些特殊的@generate
的装饰器,并且似乎使用协程进行单子解析。这一切都很好,但我的目标是了解我对 Python 中可用的基本评估策略有哪些选择。
我还发现了类似的东西lazy_object_proxy.Proxy
,但它似乎并没有帮助以更简洁的方式实例化这些对象。
那么,有没有更好的方法来按名称传递参数并避免相互递归定义的值的爆炸?