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

时间:2017-11-10 10:31:36

标签: python expression grouping parentheses

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

"{
  "title" : "risk",
  "name"  : "David"
}"

这些是用户可以编写以配置SQL查询的表达式,因此上述内容最终将表示为"{ "title" : "risk", "name" : "John", "city" : "paris" }"

我试过了:

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

condition_c & ( condition_a | condition_b )

我希望这段代码:

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 不会被调用。

有没有办法实现我想要的?您可以检查我想要的预期内容(的工作方式,我只是在表达式的一部分没有括号)。

1 个答案:

答案 0 :(得分:3)

不,表达式中的(...)括号不是调用表达式。他们只是将你的表达分组。

Python不保留分组括号。它们仅由解析器用于构建抽象语法树,并将影响分组为执行的表达式。

表达式

A & B | C

被解析为BinOp个对象的树,如下所示:

      |
    /   \
   &      C
 /   \
A     B

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

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

   &
 /   \
A     |
    /   \
  B       C

变为

|

括号已经消失,剩下的就是表达式树,Python知道你需要以什么顺序执行这些表达式;首先在BC执行&,然后在>>> 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()) ) ) 运算符中使用此表达式的结果。

您可以使用ast module

查看这些AST对象
Module

(我解开了外部ExprBinOp对象以关注>>> 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 树,并手动缩进嵌套对象。

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

LOAD_NAME

BINARY_OR在堆栈上放置一个值,BINARY_ANDclass 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 从堆栈中取出前2个值并推回结果。)

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

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

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 ) 对象时,总是得到括号:

# 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)

这是,因为SQL解析器以相同的方式处理括号;只是为了分组表达。

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

>>> 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 )

现在括号只在表达式需要时出现:

0

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

我也强烈敦促你看看SQLAlchemy,它已经实现了所有这些,等等。