我想定义基于 Python 的约束规范语言。例如:
x = IntVar()
c = Constraint(x < 19)
c.solve()
在这里IntVar
是一个描述变量的类,该变量可以采用任何整数值,并且 Constraint
是表示约束的类。为了实现这个,我可以重载运算符 <
通过定义方法 __lt__
上课 IntVar
.
现在假设我要声明 10 < x < 19
.我想写这样的东西:
c = Constraint(x > 10 and x < 19)
不幸的是,我不能这样做,因为and
不能在 Python 中重载。使用 &
而不是 and
不是一个选项,因为它的优先级和按位 &
在约束语言中有其正确的含义,例如 (x & 0x4) == 1
.
您有什么建议?
作为变通方法,我使用带引号的表达式来约束:
c = Constraint("x < 19")
但这需要实现我宁愿避免的约束语言解析,而且更重要的是,只有在实际完成解析时才能检查句法正确性。因此,用户可能要花几个小时才能发现约束定义中存在语法错误。
我考虑的另一个选择是使用一种 lambda 表达式来定义约束:
c = Constraint(lambda: x < 19)
但我无法访问 lambda 对象的解析树。
最佳答案
使用 &
, |
和 ~
其实是一个很不错的选择。您只需记录由于不同的运算符优先级而需要括号。
例如,SQLAlchemy 就是这样做的。对于不喜欢这种滥用位运算符的人,它还提供了and_(*args)
。 , or_(*args)
, 和 not_(arg)
功能与他们的运营商同行做同样的事情。但是,您被迫添加前缀符号 ( and_(foo, bar)
),这不如中缀符号 ( foo & bar
) 可读。
lambda
方法也是一个好主意(除了 lambda
本身引入的丑陋之外)。不幸的是,如果没有源代码,AST 确实不可用 - 但是等等,您确实有源代码,只是没有附加到函数对象!
想象一下这段代码:
import ast
import inspect
def evaluate(constraint):
print ast.dump(ast.parse(inspect.getsource(constraint)))
evaluate(lambda x: x < 5 and x > -5)
这会给你这个 AST:
Module(
body=[
Expr(
value=Call(
func=Name(id='evaluate', ctx=Load()), args=[
Lambda(
args=arguments(
args=[
Name(id='x', ctx=Param())
],
vararg=None,
kwarg=None,
defaults=[]
),
body=BoolOp(
op=And(),
values=[
Compare(
left=Name(id='x', ctx=Load()),
ops=[Lt()],
comparators=[Num(n=5)]
),
Compare(
left=Name(id='x', ctx=Load()),
ops=[Gt()],
comparators=[Num(n=-5)]
)
]
)
)
],
keywords=[],
starargs=None,
kwargs=None
)
)
]
)
缺点是您获得了整个源代码行 - 但您可以轻松地遍历 AST 直到到达您的 lambda 表达式(对评估函数的调用中的第一个表达式),然后您可以只处理相关部分。
为了避免自己评估它,您现在可以简单地重写 AST 以使用按位运算符,然后将新的 AST 编译为一个函数,该函数将使用可重载运算符。
让我们看一下 ((x < 5) & (x > -5))
的 AST :
body=BinOp(
left=Compare(
left=Name(id='x', ctx=Load()),
ops=[Lt()],
comparators=[Num(n=5)]
),
op=BitAnd(),
right=Compare(
left=Name(id='x', ctx=Load()),
ops=[Gt()],
comparators=[Num(n=-5)]
)
)
如您所见,差异非常小。您只需重写 AST 的 BoolOp 即可使用 BinOp!
and_(x < 5, x > -5)
的 AST看起来像这样:
body=Call(
func=Name(id='and_', ctx=Load()),
args=[
Compare(
left=Name(id='x', ctx=Load()),
ops=[Lt()],
comparators=[Num(n=5)]
),
Compare(
left=Name(id='x', ctx=Load()),
ops=[Gt()],
comparators=[Num(n=-5)]
)
],
keywords=[],
starargs=None,
kwargs=None
)
重写也不难。
关于python - 在 Python 中为表达式定义新的语义,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25469423/