Dmi*_* K. 6 python operator-overloading
我想基于Python定义约束规范语言.例如:
x = IntVar()
c = Constraint(x < 19)
c.solve()
Run Code Online (Sandbox Code Playgroud)
这IntVar是一个描述可以采用任何整数值的变量的类,并且Constraint是一个表示约束的类.为了实现这一点,我可以<通过定义__lt__类的方法来重载操作符IntVar.
现在假设我想说明这一点10 < x < 19.我想写一些类似的东西:
c = Constraint(x > 10 and x < 19)
Run Code Online (Sandbox Code Playgroud)
不幸的是,我不能这样做,因为and不能在Python中重载.使用&而不是and因为它的优先级而不是一个选项,并且因为&在约束语言中逐位具有其正确的含义,例如,(x & 0x4) == 1.
您能建议什么解决方案?
作为一种解决方法,我使用带引号的表达式来约束:
c = Constraint("x < 19")
Run Code Online (Sandbox Code Playgroud)
但这需要实现我希望避免的约束语言解析,更重要的是,只有在实际完成解析时才能检查语法正确性.因此,用户可能花费几个小时来发现约束定义中存在语法错误.
我考虑的另一个选项是使用lambda表达式进行约束定义:
c = Constraint(lambda: x < 19)
Run Code Online (Sandbox Code Playgroud)
但我无法访问lambda-object的解析树.
使用&, |and~实际上是一个不错的选择。您只需记录由于不同的运算符优先级而需要括号。
例如,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)
Run Code Online (Sandbox Code Playgroud)
这会给你这个 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
)
)
]
)
Run Code Online (Sandbox Code Playgroud)
缺点是您可以获得整个源代码行 - 但您可以轻松地遍历 AST,直到到达 lambda 表达式(对评估函数的调用中的第一个表达式),然后您可以只处理相关部分。
为了避免必须自行计算它,您现在可以简单地重写 AST 以使用按位运算符,然后将新的 AST 编译为一个函数,该函数随后将使用可重载运算符。
让我们看一下 的 AST ((x < 5) & (x > -5)):
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)]
)
)
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,差异非常小。您只需要重写 AST 的 BoolOp 即可使用 BinOp!
的 ASTand_(x < 5, x > -5)看起来像这样:
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
)
Run Code Online (Sandbox Code Playgroud)
重写也不太难。
| 归档时间: |
|
| 查看次数: |
184 次 |
| 最近记录: |