如何捕获表达式中的分组?

How to capture grouping in an expression?

我正在尝试记录表达式中的分组,例如:

condition_a = Condition( "x = 5 ")
condition_b = Condition( "y > 6 " )
condition_c = Condition( "z = 7 " )

condition_c & ( condition_a | condition_b )

这些是用户可以编写的用于配置 SQL 查询的表达式,因此以上内容最终将表示为 x = 5 AND (y > 6 OR z = 7).

我试过这个:

class Condition( object ) :
    def __init__( self, expr ) :
        ....

    def __and__( self , other ) :
        print 'calling and'
        ....
        return self

    def __or__( self , other ) : 
        print 'calling or'
        ....
        return self

    def __call__( self , other ) :
        print 'calling ()'
        ....
        return self

我希望这段代码:

condition_a = Condition( "x = 5 ")
condition_b = Condition( "y > 6 " )
condition_c = Condition( "z = 7 " )
condition = condition_c & ( condition_a | condition_b )
expected =  "z = 7 AND ( x = 5 OR y > 6 )" 

打印 calling () 但它没有。我刚看到:

calling or
calling and

好像我没有使用调用,所以像condition_a(1)这样的东西,所以__call__没有被调用。

有没有办法实现我想要的?您可以检查我想要的预期(andor 有效,只是我没有括号表达方式)。

不,表达式中的 (...) 括号不是调用表达式。他们只是在那里对你的表情进行分组。

Python 不保留分组括号。它们只被解析器用来构建一个抽象语法树,并且分组影响表达式的执行顺序。

表达式

A & B | C

被解析成 BinOp 个对象的树,像这样:

      |
    /   \
   &      C
 /   \
A     B

& 在这里绑定得更紧密,因此 A & B| 运算符执行之前执行。那是因为 | operator has a lower precedence than &.

括号告诉解析器在将 (...) 之间的所有内容放入树之前为它们构建单独的树,因此表达式

A & (B | C)

变成

   &
 /   \
A     |
    /   \
  B       C

括号没有了,剩下的就是表达式树,Python知道你需要以什么顺序执行这些表达式; | 首先在 BC 上执行,然后在 & 运算符中使用此表达式的结果。

您可以使用 ast module:

查看那些 AST 对象
>>> import ast
>>> print(ast.dump(ast.parse('A & B | C').body[0].value)))
BinOp(
    left=BinOp(
        left=Name(id='A', ctx=Load()),
        op=BitAnd(),
        right=Name(id='B', ctx=Load())
    ),
    op=BitOr(),
    right=Name(id='C', ctx=Load()))
)
>>> print(ast.dump(ast.parse('A & (B | C)').body[0].value))
BinOp(
    left=Name(id='A', ctx=Load()),
    op=BitAnd(),
    right=BinOp(
        left=Name(id='B', ctx=Load()),
        op=BitOr(),
        right=Name(id='C', ctx=Load())
    )
)

(我展开了外部 ModuleExpr 对象以关注 BinOp 树,并手动缩进了嵌套对象)。

当您使用 dis module 查看生成的字节码时,您可以看到操作顺序发生了变化:

>>> dis.dis(compile('A & B | C', '', 'eval'))
  1           0 LOAD_NAME                0 (A)
              2 LOAD_NAME                1 (B)
              4 BINARY_AND
              6 LOAD_NAME                2 (C)
              8 BINARY_OR
             10 RETURN_VALUE
>>> dis.dis(compile('A & (B | C)', '', 'eval'))
  1           0 LOAD_NAME                0 (A)
              2 LOAD_NAME                1 (B)
              4 LOAD_NAME                2 (C)
              6 BINARY_OR
              8 BINARY_AND
             10 RETURN_VALUE

(LOAD_NAME将一个值压入栈中,BINARY_ORBINARY_AND从栈中取出前2个值并推回结果)。

所有这些意味着无法通过这种方式检索括号。他们走了,他们只是在那里通知操作顺序。

然后您需要做的是维护语法树;您仍然可以从执行中重建它。您可以 always 在输出结果时包含括号:

class Expr(object):
    def __or__(self, other):
        return BinOp(self, '|', other)

    def __and__(self, other):
        return BinOp(self, '&', other)

class BinOp(Expr):
    def __init__(self, left, oper, right):
        self.left, self.oper, self.right = left, oper, right

    def __repr__(self):
        return "({} {} {})".format(self.left, self.oper, self.right)

class Condition(Expr):
    def __init__(self, expr):
        self.expr = expr

    def __repr__(self):
        return self.expr

打印 BinOp 对象时,您将总是 得到括号:

>>> condition_a = Condition( "x = 5 ")
>>> condition_b = Condition( "y > 6 " )
>>> condition_c = Condition( "z = 7 " )
>>> condition_c & ( condition_a | condition_b )
(z = 7  | (x = 5  | y > 6 ))
>>> condition_c & condition_a | condition_b
((z = 7  | x = 5 ) | y > 6 )

这是很好,因为SQL解析器以同样的方式处理括号;只是对表达式进行分组。

下一步是将 precedence 信息添加到二元运算符中。然后您可以决定是否必须在子表达式周围加上括号;只有当左边的子表达式有较低的优先级,或者右边有较低或相等的优先级时,你才需要它们:

# Condition stays the same

class Expr(object):
    precedence = float('inf')

    def __or__(self, other):
        return BinOp(self, '|', other)

    def __and__(self, other):
        return BinOp(self, '&', other)

class BinOp(Expr):
    def __init__(self, left, oper, right):
        self.left, self.oper, self.right = left, oper, right
        self.precedence = '|&'.index(oper)  # 0 or 1

    def __repr__(self):
        left, right = self.left, self.right
        if left.precedence < self.precedence:
            left = '({})'.format(left)
        if right.precedence <= self.precedence:
            right = '({})'.format(right)
        return "{} {} {}".format(left, self.oper, right)

现在括号仅在表达式需要时显示:

>>> condition_c & ( condition_a | condition_b )
z = 7  & (x = 5  | y > 6 )
>>> condition_c & condition_a | condition_b
z = 7  & x = 5  | y > 6
>>> condition_c & (condition_a & condition_b)
z = 7  & (x = 5  & y > 6 )

如果您要扩展此系统(添加更多运算符类型),则必须扩展那里的优先级概念;上面的例子只有 01 用于二元运算符,但是如果你有更多的值,你必须自然地扩展这些值。

我还 强烈 敦促您查看 SQLAlchemy,它已经实现了所有这些,以及更多。