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

Python中的非严格按名称参数?,python,parser-combinators,callbyname,evaluation-strategy,Python,Parser Combinators,Callbyname,Evaluation Strategy,问题 有没有办法将函数参数声明为非严格(已传递) 如果这不能直接实现:是否有任何帮助函数或装饰器可以帮助我实现类似的功能 具体例子 这里有一个小玩具的例子来试验 假设我想构建一个小型解析器组合器库,它可以处理以下带括号的算术表达式的经典语法(为了简单起见,数字被单个文本值1替换): 假设我将解析器组合器定义为一个对象,该对象具有一个方法parse,该方法可以获取标记列表、当前位置,并抛出一个解析错误,或者返回一个结果和一个新位置。我可以很好地定义一个parsercompbinator基类,它提

问题

有没有办法将函数参数声明为非严格(已传递)

如果这不能直接实现:是否有任何帮助函数或装饰器可以帮助我实现类似的功能


具体例子

这里有一个小玩具的例子来试验

假设我想构建一个小型解析器组合器库,它可以处理以下带括号的算术表达式的经典语法(为了简单起见,数字被单个文本值
1
替换):

假设我将解析器组合器定义为一个对象,该对象具有一个方法
parse
,该方法可以获取标记列表、当前位置,并抛出一个解析错误,或者返回一个结果和一个新位置。我可以很好地定义一个
parsercompbinator
基类,它提供
+
(串联)和
(可选)。然后我可以定义接受常量字符串的解析器组合,并实现
+

# 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)
这确实让我能够以一种非常接近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,而不触发对无限相互递归的热切求值,那就太好了


我尝试过的

我已经检查了核心语言中的可用内容:似乎只有
如果
可以“短路”,如果我错了,请纠正我

我试着看看其他非玩具示例库是如何做到这一点的

  • 比如说,, 使用显式前向声明避免相互递归 (查看
    forward\u decl
    值。定义
    github README.md示例代码中的部分)

  • parsec.py
    使用一些特殊的
    @generate
    装饰器 似乎在使用协同程序进行一元解析。 这一切都很好,但我的目标是了解哪些选项 关于可用的基本评估策略,我有很多建议 在Python中

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


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

这是一个好主意,但Python的语法不允许这样做:Python表达式总是严格求值(除了
if
块和
短路表达式)

特别是,问题在于,在以下表达式中:

num = p(lit("1"))
p
函数参数始终与绑定到同一对象的新名称一起接收。计算
lit(“1”)
得到的对象没有任何名称(直到通过
p
的形式参数创建名称),因此那里没有可绑定的名称。相反,那里必须有一个对象,否则
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
,它返回一个
DeferredLookup
对象,该对象将使用
getattr(locals(),'lit')
解析其实际使用的成员。请注意,这捕获了
locals()
,这可能是您不想要的;您可以将其调整为使用lambda,或者更好的做法是在其他名称空间中创建所有实体


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

针对必须只接受一个按名称参数的函数的特殊解决方案

如果要定义函数<代码> f>代码>,必须按名称取一个参数,考虑将<代码> f>代码>为<代码> @装饰器< /代码>,而不是用<代码> lambdas < /> > >,装饰器可以直接接收函数定义。


问题中出现的
lambdas
,是因为我们需要一种使右侧执行变慢的方法。但是,如果我们将非终端符号的定义更改为
def
s而不是局部变量,则RHS也不会立即执行。然后我们要做的是将这些
def
s转换为
parsercompbinator
s。为此,我们可以使用decorator


我们可以定义一个decorator,将函数包装成
lazyparsercompbinator
,如下所示:

def rule(f):
  return LazyParserCombinator(f)
然后将其应用于包含每个语法规则定义的函数:

@rule 
def num():    return lit("1")

@rule 
def factor(): return num | (lit("(") + expr + lit(")"))

@rule 
def term():   return factor + lit("*") + term | factor

@rule 
def expr():   return (term + lit("+") + expr) | term
规则右侧的语法开销最小(不需要引用其他规则的开销,不需要
p(…)
-wrappers或
ruleName()
-括号),并且没有与lambdas相反的直观样板文件


说明:

给定一个高阶函数
h
,我们可以使用它来装饰其他函数
f
,如下所示:

@h
def f():
  <body>
@h
def():
这基本上是:

def f():
  <body>

f = h(f)
def():
f=h(f)

而且
h
不受返回函数的限制,它还可以返回其他对象,比如上面的
parsercompbinator

很抱歉可能会有一个愚蠢的后续问题,但是:
\uuu getattr\uuuu
是修改成员访问
x.y
工作方式的特殊方法,但是
\uu命名空间
不是特别的,它是这只是您提出的一个变量名,对吗?重写
\uuuu getattr\uuuu
以将计算推迟到

@h
def f():
  <body>
def f():
  <body>

f = h(f)