Python `or` 和 `and` 运算符优先级示例

6

我无法用Python编写一个示例,展示布尔运算符优先级规则与短路求值的组合。但我可以使用以下方式展示运算符优先级:

print(1 or 0 and 0)  # Returns 1 because `or` is evaluated 2nd.

但是当我将它改为以下代码时,短路问题就出现了:
def yay(): print('yay'); return True
def nay(): print('nay')
def nope(): print('nope')
print(yay() or nay() and nope())  # Prints "yay\nTrue"

对于 or 前的表达式为 True 的 4 种可能性,只有这个表达式会被计算。如果运算符优先级有效,则应该打印 "nay\nnope\nyay\nTrue" 或者 "nay\nyay\nTrue",因为 and 运算符应该首先被计算。
从这个例子中得出的结论是,Python 从左到右读取布尔表达式,并在结果已知时终止,而不管运算符优先级如何。
请给出一个示例,其中可以看到 and 被首先计算,并且这不是由于代码从左到右被解释的原因。

4
请注意,运算符优先级与求值顺序不同。 - tobias_k
5个回答

10
你混淆了运算符优先级和求值顺序。表达式r = x or y and z不是被评估为tmp = y and z; r = x or tmp, 而只是r = x or (y and z)。这个表达式从左到右被评估,如果or的结果已经确定,那么(y and z)将根本不会被评估。请注意,如果orand是函数,则情况会有所不同; 在这种情况下,函数的参数在调用函数本身之前将被评估。因此,operator.or_(yay(), operator.and_(nay(), nope()))打印yaynaynope,即按顺序从左到右打印所有三个。你也可以将其推广到其他运算符上,两个表达式将由于不同的运算符优先级(包括隐式和显式使用(...))而产生不同的结果,但两次都是从左到右调用函数。
>>> def f(x): print(x); return x
>>> f(1) + f(2) * f(3) / f(4) ** f(5) - f(6)         # 1 2 3 4 5 6 -> -4.99
>>> (f(1) + f(2)) * (((f(3) / f(4)) ** f(5)) - f(6)) # 1 2 3 4 5 6 -> -17.29

正如评论中所指出的,虽然操作符之间的术语是从左到右进行评估的,但实际操作是根据它们的优先级进行评估的。

class F:
    def __init__(self,x): self.x = x
    def __add__(self, other): print(f"add({self},{other})"); return F(self.x+other.x)
    def __mul__(self, other): print(f"mul({self},{other})"); return F(self.x*other.x)
    def __pow__(self, other): print(f"pow({self},{other})"); return F(self.x**other.x)
    def __repr__(self): return str(self.x)
def f(x): print(x); return F(x)

这样,表达式f(1) + f(2) ** f(3) * f(4)会按顺序计算并推入堆栈:1, 2, 3, pow(2,3), 4, mul(8,4), add(1,32)。表达式参数一旦被计算,表达式就被立即计算。


我知道在将参数传递给函数之前,所有的参数都必须被求值。但是你提到的operator.or_operator.and_的例子却是一个有趣的反例,它表明了使用operator模块中的函数无法复制andor运算符的求值行为。所以运算符优先级只涉及隐式括号嵌套,而不涉及求值顺序,对吗? - WloHu
在你的例子中,函数按从左到右的顺序调用,但运算符的评估按优先级顺序进行:因此定义一个实现算术运算符并打印它们的类,你会发现计算不是从左到右的。事实上,如果你这样做,甚至会看到 f(1) ** f(2) ** f(3) 从右到左调用了 __pow__ 方法。 - Duncan
@Duncan 是的,操作会按照优先级进行评估,我并不是想否认这一点。希望我的编辑能够澄清这个问题。 - tobias_k
有人可能会认为这里没有进行从右到左的计算,但实际上只是因为当运算符被调用时表达式的数量变少了,因此行变短了。你并不是有意地回头,就像逆着电梯方向走一样。 - WloHu

2
yay()返回的第一个值为True,因此Python甚至不会运行表达式的其余部分。这是为了提高效率,因为表达式的其余部分不会影响结果。
看下面的例子,我改变了顺序:
def yay(): print('yay'); return True
def nay(): print('nay')
def nope(): print('nope')
print(nay() and nope() or yay())

输出:

nay
yay
True

这里发生了什么?nay()返回None,它是假的,所以我们已经知道nope()返回什么都无所谓(因为它是一个and条件,第一部分已经是False) - 所以我们不运行它。然后运行yay(),因为它可以改变表达式已经是False的值 - 它返回True

这是短路的好例子,但它并没有展示运算符优先级和求值顺序之间的区别。这就是为什么我要求使用 and,因为它不会被首先求值,这是由于从左到右的求值顺序所决定的。 - WloHu
短路求值不仅仅是为了效率。它还允许构造这样的表达式,即第二部分在第一部分没有特定结果时是无意义的,例如 b != 0 and a/b > 1 - Lutz Prechelt

2

or的优先级低于and,因此a or b and c被解释为a or (b and c)

此外,逻辑运算符是“懒惰”计算的,所以如果aTruea or b将不会导致b的计算。


1
实际上,你的代码返回了 1 ,不是因为 or 被第二次评估,而是因为 1 是真的,不需要进一步评估。这使得行为保持一致。

1
Python从左到右评估表达式,并在结果已知时停止。例如,在or运算符的情况下,如果左侧实体为True,则可以肯定该运算符将返回true,因此在此情况下不会评估右侧的表达式。
在and运算符的情况下,如果左侧表达式为False,则可以确定运算符应返回False。因此,在此处不会评估右侧的表达式。
这就是您示例中发生的情况。

它不会从左到右评估所有内容:a if cond else b在评估ab之前会评估cond - Duncan
我不太理解这里的语法。你能稍微解释一下吗?我有点好奇你想说什么。 - Abhishek Arya
1
我只是想说,虽然 Python 中的大多数表达式从左到右进行求值,但也有例外。条件表达式先评估条件,然后评估左侧或右侧的一个表达式,因此当条件为真时,它不能遵循从左到右的规则。请参阅 https://docs.python.org/3/reference/expressions.html#conditional-expressions - Duncan
谢谢你告诉我。 - Abhishek Arya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接