问题
有没有办法将函数参数声明为非严格(已传递
by-name
)?
如果这不能直接实现:是否有任何帮助函数或装饰器可以帮助我实现类似的功能?
具体实例
这里有一个小玩具的例子来进行实验。
假设我想构建一个小型解析器组合器库,它可以处理以下带括号的算术表达式的经典语法(数字被单个文字值替换
1
为简单起见):
num = "1"
factor = num
| "(" + expr + ")"
term = factor + "*" + term
| factor
expr = term + "+" + expr
| term
假设我定义了
解析器组合器
作为具有方法的对象
parse
它可以获取标记列表和当前位置,并抛出解析错误,或返回结果和新位置。我可以很好地定义
ParserCombinator
提供
+
(串联)和
|
(可选)。然后我可以定义接受常量字符串的解析器组合器,并实现
+
和
|
:
# 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
使用显式前向声明避免相互递归
(请看
forward_decl
和
value.define
参与github自述。md示例代码)。
-
这个
parsec.py
使用一些特殊的
@generate
装饰师
并且似乎使用协同程序进行类似于一元语法分析的操作。
这很好,但我的目标是了解哪些选项
关于可用的基本评估策略
在Python中。
我还发现了类似
lazy_object_proxy.Proxy
,但以更简洁的方式实例化这些对象似乎没有什么帮助。
那么,有没有更好的方法按名称传递参数并避免相互递归定义的值膨胀?