gpt4 book ai didi

python - Python 中的非严格按名称参数?

转载 作者:太空宇宙 更新时间:2023-11-03 15:46:24 27 4
gpt4 key购买 nike

问题

有没有办法将函数参数声明为非严格的(通过 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,而不触发对无限相互递归的热切求值,那就太好了。


我尝试过的

我查看了核心语言中的可用内容:似乎只有ifandor可以“短路” ,如有错误请指正。

我已经尝试查看其他非玩具示例库是如何做到这一点的。

  • 例如, funcparserlib使用明确的前向声明来避免相互递归(查看 forward_declvalue.define github README.md 示例代码中的一部分)。

  • parsec.py 使用一些特殊的 @generate 装饰器并且似乎使用协程做一些类似单子(monad)解析的事情。这一切都很好,但我的目标是了解哪些选项我有关于可用的基本评估策略在 Python 中。

我还发现了类似 lazy_object_proxy.Proxy 的东西,但它似乎无助于以更简洁的方式实例化此类对象。

那么,有没有更好的方法来按名称传递参数并避免相互递归定义的值爆炸?

最佳答案

这是个好主意,但它不是 Python 语法所允许的:Python 表达式总是被严格求值(除了 if block 和 and 短路表达式)。

特别是,问题是在这样的表达式中:

num = p(lit("1"))

p 函数参数总是以绑定(bind)到同一对象的新名称接收。评估 lit("1") 的对象没有命名任何东西(直到通过 p 的形式参数创建名称),所以那里没有名字可以绑定(bind)。相反,那里必须有一个对象,否则 p 根本无法接收值。

您可以做的是添加一个新对象来代替 lambda 来延迟对名称的评估。例如,像这样的东西:

class DeferredNamespace(object):
def __init__(self, namespace):
self.__namespace = namespace
def __getattr__(self, name):
return DeferredLookup(self.__namespace, name)

class DeferredLookup(object):
def __init__(self, namespace, name):
self.__namespace = namespace
self.__name = name
def __getattr__(self, name):
return getattr(getattr(self.__namespace, self.__name), name)

d = DeferredNamespace(locals())

num = p(d.lit("1"))

在这种情况下,d.lit 实际上不返回 lit,它返回一个将使用 getattr 的 DeferredLookup 对象(locals(), 'lit') 在实际使用时解析其成员。请注意,这会急切地捕获 locals(),您可能不希望这样做;您可以调整它以使用 lambda,或者更好的是,无论如何只需在其他 namespace 中创建所有实体。

您仍然会在语法中遇到 d. 的缺点,这可能会或可能不会破坏交易,具体取决于您使用此 API 的目标。

关于python - Python 中的非严格按名称参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49674875/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com