python - 在 Python 中为表达式定义新的语义

标签 python operator-overloading

我想定义基于 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/

相关文章:

C++——赋值运算符的签名不正确?

Python:如何向数据帧的索引和列添加缺失值?

python 2.7 等效于内置方法 int.from_bytes

c++ - 为什么我不能重载 ostream 的 << 运算符?

c++ - 为数学函数 C++ 实现乘法运算符

c# - C# 中有什么方法可以在派生类中强制执行运算符重载吗?

python - 将程序作为可执行文件运行时设置默认 python 版本 ./xxx.py - 在 linux 上

Python ssl 登录卡在 Debian 上

Python:time.time() 与 time.clock() 之间有显着差异吗?

c++ - 重载新运算符和继承