Python 运算符优先级

9
Python文档中提到*/的优先级相同。
我知道Python表达式从左到右进行计算。您是否可以依赖于此,并假设j*j/m始终等于(j*j)/m而避免使用括号?如果是这样,那么我可以假设这适用于具有相同优先级的运算符吗?

注:对于我的目的来说,这个问题很好,我在阅读仅包含整数的代码(如上面的示例)时避免使用括号,这让我感到非常怀疑。

7
"明确胜于含糊。","面对不确定性,拒绝猜测的诱惑。" - PEP 20 - msw
如果你正在阅读“仅整数代码(integer-only code)”,那么你可能会面临一个更大的问题:这段代码打算使用哪个版本的Python来运行?它是否在开头包含了from __future__ import division?应该将/解释为和/或更改为//吗? - John Machin
@John Machin:在这种情况下,“/”表示整数除法,如果是Python 2.6没有使用“from future”语句,我注意到像jj/m (jj)/m这样的代码是相等的,但是对于这种行为的一致性感到担心。 - Lord British
现在我可以阅读并理解像这样的代码 jm/jj/j 的含义。 - Lord British
3个回答

14
是的 - 具有相同优先级的不同运算符是左结合的;也就是说,将对最左边的两个项目进行操作,然后是结果和第3个项目,依此类推。
例外情况是 `**` 运算符:
>>> 2 ** 2 ** 3
256

此外,比较操作符 (==, >等) 不会像结合律那样进行操作,而是将 x [cmp] y [cmp] z 转换成 (x [cmp] y) and (y [cmp] z)


6
在Python中,"Assignment"并不是一个表达式,而=号在常规意义上也不是一个运算符;操作符优先级不适用于它。在语法上,将同一对象分配给多个目标被明确定义为多目标赋值。 - Thomas Wouters
2
赋值有误,请更换示例!在 Python 中唯一右结合的运算符是 **,例如 2**3**4 - Nas Banov
@Amber:(1)如@Nas Banov所说,**是仅有的Python右结合运算符 (2)它与自己右结合是事实[1]的不起眼的结果(3)比较运算符是无结合性的... a < b < c 意味着既不是 (a < b) < c 也不是 a < (b < c) --- 请考虑最终编辑(删除您的答案)。 - John Machin
1
@John:老实说,我不是为了声望而做这个;即使我这样做了,我相信我已经达到了每日的声望上限,与这个问题无关。我不明白删除现在已经成为对OP问题简明且正确回答的意义所在。 - Amber
除了 y 只被评估一次之外,其他都一样。 - Mechanical snail
显示剩余3条评论

14

如果你作为编码人员对某个表达式的歧义感到困惑并需要询问,那么你就应该预料到读者也会同样感到困惑,为了增加可读性,建议添加一些括号。

如果你是编译器,依靠运算符优先级规则是很好的。

针对评论的回答:

当代码阅读者遇到需要外部协助来确认的不确定性时,你应该假设下一个读者比你更不懂,并为他们节省时间和避免可能出现的人为错误,主动添加括号以增加可读性。

实际上,即使被接受的答案也是错误的(在理论上,而非效果上,详见其第一个评论),这是我之前没有意识到的,甚至一小部分投票者也没有察觉到。

至于关于基本代数的说法,在OP中使用的特定示例非常有指导意义。无论运算符优先级如何,表达式j * (j / m)在代数上等同于(j * j) / m。但是,不幸的是,Python代数运算只是“柏拉图理想”代数的近似值,根据jm的大小可能会导致任一形式的错误答案。例如:

>>> m = 1e306
>>> m
1e+306
>>> j = 1e307
>>> j
9.9999999999999999e+306
>>> j / m
10.0
>>> j*j
inf
>>> j * (j / m)
1e+308
>>> (j * j) / m
inf
>>> ((j * j) / m) == (j * (j/m))
False

事实上,Python(以及我的FPU)拟代数的恒等属性不成立。根据文档说明,在您的计算机上可能会有所不同:

使用C中的double实现浮点数。除非您知道正在使用的机器,否则精度方面的所有赌注都将关闭。

可以说,在溢出的棘手边缘工作是毫无意义的,这在某种程度上是正确的,但在去除上下文情况后,该表达式在一种运算顺序下是不确定的,而在另一种下则是“正确的”。


2
你为什么认为我在试图编写代码?如果我是在尝试阅读呢? - Lord British
1
我的看法是:在仅涉及 +-*/ 运算符的表达式中期望冗余括号是荒谬的……优先级和结合性(隐含地)在学校的代数课程中教授,不是吗?对于计算机程序员来说,这被认为是基本知识,是或否?另一方面,在项目或团队标准中强制使用括号来包围像位移、位与、位或等很少使用且在不同语言中具有不同优先级的操作应该是必需的。 - John Machin
1
如果有疑问,那么你应该总是使用括号。实际上,OP表示他不确定哪种优先级适用,因此在这种情况下依赖它们是正确的。 - Paddy3118
@msw:我在问题中添加了一条评论。 - Lord British
@paddy3118:这与“不确定”无关。OP实际上是在说他已经阅读了Python文档“Python文档说*和/具有相同的优先级”,但怀疑实现的可靠性“我可以依赖[它]吗……?”——这是一个非常不同的问题,答案是“是的,在Python中和[几乎所有]其他语言中都是如此”。 - John Machin
显示剩余2条评论

3

简短回答:是的。

Python文档如下所述:

同一方框内的运算符具有相同的优先级。除非明确给出语法,否则运算符为二元运算符。同一方框内的运算符从左到右分组(除了比较,包括测试,它们都具有相同的优先级并从左到右链接...以及指数,它们从右到左分组)。

因此,换句话说,答案是肯定的,具有相同优先级的运算符将从左到右分组,而不是比较运算符会链接而不是分组

>>> x = 0
>>> y = 0
>>> x == y == True
False
>>> (x == y) == True
True
>>> x == (y == True)
True

和指数:

>>> 2 ** 2 ** 3
256
>>> (2 ** 2) ** 3
64
>>> 2 ** (2 ** 3)
256

此外,在赋值时,右侧表达式会在左侧表达式之前被求值:
>>> x = 1
>>> y = x = 2
>>> y
2

3
赋值语句不是一个运算符(请参见上文),实际上,目标变量的计算顺序并不是从右到左。赋值语句右侧的表达式会先被计算,然后目标变量会按照从左到右的顺序进行计算,并进行赋值。你可以通过检查例如a().a, b().b = c().c中函数调用的顺序来看到这种区别:你会看到c()会被调用,然后获取属性c,接着调用a()并将a属性赋值,最后再对b()做同样的操作。 - Thomas Wouters
我并不是想暗示赋值是一个运算符,这就是为什么它排在最后的原因。我稍微改了一下关于赋值的措辞。 - David Webb
你的措辞仍然暗示了xy的评估顺序对分配给y的结果很重要。实际上并不是这样。xy的值从未参与赋值。y = x = 2严格意义上意味着“将2分配给yx”,而不是“将2分配给x,然后将x的结果值分配给y"(当您涉及更复杂的赋值目标时,例如使用将自己设置为与分配给它们的不同值的属性时,可以看到这一点)。 - Thomas Wouters
这个措辞直接来自Python文档:http://docs.python.org/reference/expressions.html#evaluation-order - David Webb

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