如何捕获表达式中的分组?
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__
没有被调用。
有没有办法实现我想要的?您可以检查我想要的预期(and 和 or 有效,只是我没有括号表达方式)。
不,表达式中的 (...)
括号不是调用表达式。他们只是在那里对你的表情进行分组。
Python 不保留分组括号。它们只被解析器用来构建一个抽象语法树,并且分组影响表达式的执行顺序。
表达式
A & B | C
被解析成 BinOp
个对象的树,像这样:
|
/ \
& C
/ \
A B
&
在这里绑定得更紧密,因此 A & B
在 |
运算符执行之前执行。那是因为 |
operator has a lower precedence than &
.
括号告诉解析器在将 (...)
之间的所有内容放入树之前为它们构建单独的树,因此表达式
A & (B | C)
变成
&
/ \
A |
/ \
B C
括号没有了,剩下的就是表达式树,Python知道你需要以什么顺序执行这些表达式; |
首先在 B
和 C
上执行,然后在 &
运算符中使用此表达式的结果。
您可以使用 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())
)
)
(我展开了外部 Module
和 Expr
对象以关注 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_OR
和BINARY_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 )
如果您要扩展此系统(添加更多运算符类型),则必须扩展那里的优先级概念;上面的例子只有 0
和 1
用于二元运算符,但是如果你有更多的值,你必须自然地扩展这些值。
我还 强烈 敦促您查看 SQLAlchemy,它已经实现了所有这些,以及更多。
我正在尝试记录表达式中的分组,例如:
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__
没有被调用。
有没有办法实现我想要的?您可以检查我想要的预期(and 和 or 有效,只是我没有括号表达方式)。
不,表达式中的 (...)
括号不是调用表达式。他们只是在那里对你的表情进行分组。
Python 不保留分组括号。它们只被解析器用来构建一个抽象语法树,并且分组影响表达式的执行顺序。
表达式
A & B | C
被解析成 BinOp
个对象的树,像这样:
|
/ \
& C
/ \
A B
&
在这里绑定得更紧密,因此 A & B
在 |
运算符执行之前执行。那是因为 |
operator has a lower precedence than &
.
括号告诉解析器在将 (...)
之间的所有内容放入树之前为它们构建单独的树,因此表达式
A & (B | C)
变成
&
/ \
A |
/ \
B C
括号没有了,剩下的就是表达式树,Python知道你需要以什么顺序执行这些表达式; |
首先在 B
和 C
上执行,然后在 &
运算符中使用此表达式的结果。
您可以使用 ast
module:
>>> 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())
)
)
(我展开了外部 Module
和 Expr
对象以关注 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_OR
和BINARY_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 )
如果您要扩展此系统(添加更多运算符类型),则必须扩展那里的优先级概念;上面的例子只有 0
和 1
用于二元运算符,但是如果你有更多的值,你必须自然地扩展这些值。
我还 强烈 敦促您查看 SQLAlchemy,它已经实现了所有这些,以及更多。